How to check if element contains specific class attribute - java

How can I check if a selenium web element contains a specific css class.
I have this html li element
<li class="list-group-item ng-scope active" ng-repeat="report in lineageController.reports" ng-click="lineageController.activate(report)" ng-class="{active : lineageController.active == report}">
As you can see inside class attribute there is an active class.
My problem is that I have this element and I want to do a check based on if the class attribute has that "active" value among the others, being more elegant solution then using xpath.
How can I do this?

Given you already found your element and you want to check for a certain class inside the class-attribute:
public boolean hasClass(WebElement element) {
String classes = element.getAttribute("class");
for (String c : classes.split(" ")) {
if (c.equals(theClassYouAreSearching)) {
return true;
}
}
return false;
}
#EDIT
As #aurelius rightly pointed out, there is an even simpler way (that doesn't work very well):
public boolean elementHasClass(WebElement element, String active) {
return element.getAttribute("class").contains(active);
}
This approach looks simpler but has one big caveat:
As pointed out by #JuanMendes you will run into problems if the class-name you're searching for is a substring of other class-names:
for example class="test-a test-b", searching for class.contains("test") will return true but it should be false
#EDIT 2
Try combining the two code snippets:
public boolean elementHasClass(WebElement element, String active) {
return Arrays.asList(element.getAttribute("class").split(" ")).contains(active);
}
That should fix your caveat.

The answer provided by #drkthng works but you might have a case where the class name is a subset of another class name. For example:
<li class="list-group-item ng-scope active">text</li>
If you wanted to find the class "item" then the provided answer would give a false positive. You might want to try something like this:
public boolean hasClass(WebElement element, String htmlClass) {
String classes = element.getAttribute("class").split("\\s+");
if (classes != null) {
for (String classAttr: classes) {
if (classAttr.equals(htmlClass)) {
return true;
}
}
}
return false;
}

Use javascript: classList.contains
WebElement element = By.id("id");
String className = "hidden";
JavascriptExecutor js = (JavascriptExecutor) driver;
Boolean containsClass = js.executeScript("return arguments[0].classList.contains(arguments[1])", element, className);

Based on a common pre-classList javascript technique:
public boolean hasClass(WebElement element, String theClass) {
return (" " + element.getAttribute("class") + " ").contains(" " + theClass + " ");
}

Simmilar to previous one, but with java 8 capabilities:
String classes= getDriver().findElement(someSelector).getAttribute("class");
Optional<String> classFindResult = Arrays.stream(elementClasses.split(" ")).filter(el -> el.equals("myClass")).findFirst();
if(openClassFindResult.isPresent()){
return true;
}
return false;

Improving on #uesports135 answer, "classess" should be a String array.
public boolean hasClass(WebElement element, String htmlClass) {
String[] classes = element.getAttribute("class").split("\\s+");
if (classes != null) {
for (String classAttr: classes) {
if (classAttr.equals(htmlClass)) {
return true;
}
}
}
return false;
}

For anyone looking for a C# implementation:
public static class WebElementExtensions
{
private static Regex _classNameValidatorRegex = new Regex(#"^[a-z][a-z0-9\-_]*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static Regex _whiteSpaceRegex = new Regex(#"\s+");
public static bool HasClass(this IWebElement element, params string[] htmlClasses)
{
if (!htmlClasses.Any())
throw new ArgumentException("No html classes to match.");
if (!htmlClasses.All(c => _classNameValidatorRegex.IsMatch(c)))
throw new ArgumentException("Invalid CSS class(es) detected.");
var classAttribute = element.GetAttribute("class");
if (string.IsNullOrWhiteSpace(classAttribute))
return false;
var elementClasses = _whiteSpaceRegex.Split(classAttribute.Trim()).ToHashSet();
return htmlClasses.All(c => elementClasses.Contains(c));
}
public static bool HasAnyClass(this IWebElement element, params string[] htmlClasses)
{
if (!htmlClasses.Any())
throw new ArgumentException("No html classes to match.");
if (!htmlClasses.All(c => _classNameValidatorRegex.IsMatch(c)))
throw new ArgumentException("Invalid CSS class(es) detected.");
var classAttribute = element.GetAttribute("class");
if (string.IsNullOrWhiteSpace(classAttribute))
return false;
var elementClasses = _whiteSpaceRegex.Split(classAttribute.Trim()).ToHashSet();
return htmlClasses.Any(c => elementClasses.Contains(c));
}
}

Try using contains();
String classes = divPubli.getAttribute("class");
assertTrue("The element does not contain .maClass class .publi",classes.contains("maClass"));

Related

Selenium String into WebElement for iteration

I have defined my web elements as following:
By elementABC= By.xpath("//div[#id='ABC']");
By elementDEF= By.xpath("//div[#id='DEF']");
Now I have a method that should take the the name as String (elementABC,elementDEF) and verify the presence of element. The String is being read from external file excel. The reason is so that I can iterate over multiple elements on a web page and just verify there presence.
public static boolean verifyElementPresent(String e) {
boolean result= false;
WebElement webObj=e; // this is where I'm facing trouble
WebElement element = driver.findElement(webObj);
//WebElement element = driver.findElement(By.xpath("elementDEF"));
if (element.isDisplayed() ) {
result=true;
}
else {
result=false;
}
return result;
}
My problem is that I want to read a string and then be able to convert it into Web Element. The value of e being passed in my method is elementABC, elementDEF. And in next line I want to convert into WebElement.
WebElement webObj=e;
WebElement element = driver.findElement(webObj);
I came across a similar post on stackoverflow and noticed users have mentioned there is no good way to convert WebElement into String. But I still wanted to know if there is any other way of implementing this.
convert String to WebElement
You should define webObj as By not WebElement.
To prevent the script from error when element not found, you still need to use try catch, like this:
public static boolean verifyElementPresent(String e) {
boolean result= false;
By webObj = By.xpath("//div[#id='" +e +"']");
WebElement element = driver.findElement(webObj);
try {
if (element.isDisplayed()) {
result=true;
}
} catch (Exception e2) {
// TODO: handle exception
}
return result;
}

Merging same elements in JSoup

I have the HTML string like
<b>test</b><b>er</b>
<span class="ab">continue</span><span> without</span>
I want to collapse the Tags which are similar and belong to each other. In the above sample I want to have
<b>tester</b>
since the tags have the same tag withouth any further attribute or style. But for the span Tag it should remain the same because it has a class attribute. I am aware that I can iterate via Jsoup over the tree.
Document doc = Jsoup.parse(input);
for (Element element : doc.select("b")) {
}
But I'm not clear how look forward (I guess something like nextSibling) but than how to collapse the elements?
Or exists a simple regexp merge?
The attributes I can specify on my own. It's not required to have a one-fits-for-all Tag solution.
My approach would be like this. Comments in the code
public class StackOverflow60704600 {
public static void main(final String[] args) throws IOException {
Document doc = Jsoup.parse("<b>test</b><b>er</b><span class=\"ab\">continue</span><span> without</span>");
mergeSiblings(doc, "b");
System.out.println(doc);
}
private static void mergeSiblings(Document doc, String selector) {
Elements elements = doc.select(selector);
for (Element element : elements) {
// get the next sibling
Element nextSibling = element.nextElementSibling();
// merge only if the next sibling has the same tag name and the same set of attributes
if (nextSibling != null && nextSibling.tagName().equals(element.tagName())
&& nextSibling.attributes().equals(element.attributes())) {
// your element has only one child, but let's rewrite all of them if there's more
while (nextSibling.childNodes().size() > 0) {
Node siblingChildNode = nextSibling.childNodes().get(0);
element.appendChild(siblingChildNode);
}
// remove because now it doesn't have any children
nextSibling.remove();
}
}
}
}
output:
<html>
<head></head>
<body>
<b>tester</b>
<span class="ab">continue</span>
<span> without</span>
</body>
</html>
One more note on why I used loop while (nextSibling.childNodes().size() > 0). It turned out for or iterator couldn't be used here because appendChild adds the child but removes it from the source element and remaining childen are be shifted. It may not be visible here but the problem will appear when you try to merge: <b>test</b><b>er<a>123</a></b>
I tried to update the code from #Krystian G but my edit was rejected :-/ Therefore I post it as an own post. The code is an excellent starting point but it fails if between the tags a TextNode appears, e.g.
<span> no class but further</span> (in)valid <span>spanning</span> would result into a
<span> no class but furtherspanning</span> (in)valid
Therefore the corrected code looks like:
public class StackOverflow60704600 {
public static void main(final String[] args) throws IOException {
String test1="<b>test</b><b>er</b><span class=\"ab\">continue</span><span> without</span>";
String test2="<b>test</b><b>er<a>123</a></b>";
String test3="<span> no class but further</span> <span>spanning</span>";
String test4="<span> no class but further</span> (in)valid <span>spanning</span>";
Document doc = Jsoup.parse(test1);
mergeSiblings(doc, "b");
System.out.println(doc);
}
private static void mergeSiblings(Document doc, String selector) {
Elements elements = doc.select(selector);
for (Element element : elements) {
Node nextElement = element.nextSibling();
// if the next Element is a TextNode but has only space ==> we need to preserve the
// spacing
boolean addSpace = false;
if (nextElement != null && nextElement instanceof TextNode) {
String content = nextElement.toString();
if (!content.isBlank()) {
// the next element has some content
continue;
} else {
addSpace = true;
}
}
// get the next sibling
Element nextSibling = element.nextElementSibling();
// merge only if the next sibling has the same tag name and the same set of
// attributes
if (nextSibling != null && nextSibling.tagName().equals(element.tagName())
&& nextSibling.attributes().equals(element.attributes())) {
// your element has only one child, but let's rewrite all of them if there's more
while (nextSibling.childNodes().size() > 0) {
Node siblingChildNode = nextSibling.childNodes().get(0);
if (addSpace) {
// since we have had some space previously ==> preserve it and add it
if (siblingChildNode instanceof TextNode) {
((TextNode) siblingChildNode).text(" " + siblingChildNode.toString());
} else {
element.appendChild(new TextNode(" "));
}
}
element.appendChild(siblingChildNode);
}
// remove because now it doesn't have any children
nextSibling.remove();
}
}
}
}

Get list of CSS rules that apply to a specific HTML class in Java

I'm using the CSS Parser to get specific CSS rules that belong to a set HTML class. At the moment I have got a list of CSS rules in the site, however I cannot figure out how to get the rules I'm looking for.
Current Code:
InputSource inputSource = new InputSource("example.com");
CSSOMParser parser = new CSSOMParser(new SACParserCSS3());
ErrorHandler errorHandler = new CSSErrorHandler();
parser.setErrorHandler(errorHandler);
CSSStyleSheet sheet = parser.parseStyleSheet(inputSource, null, null);
CSSRuleList rules = sheet.getCssRules();
One of my options would be doing a for loop, but I'm reluctant to do this because
a. It will be slow if there is hundreds of rules in a page.
b. There appears to be no method to get the class name of a rule.
Any help would be appreciated
Add a: The CSS has already been parsed by your code, so you only have to look at the selectors which might be acceptable in terms of performance.
Add b: The CSSStyleRule interface lacks a method getSelectors() but CSSStyleRuleImpl has it. So you could try something along:
scanRules(rules, name -> name.contains("e"),
(names, rule) -> System.out.println(
new TreeSet<>(names) + " --> " + rule.getCssText()));
with recursive helper methods
// scan CSS rules including rules contained in media rules
void scanRules(CSSRuleList rules, Predicate<String> classNameTest,
BiConsumer<Set<String>, CSSStyleRule> ruleAction) {
for (int ri = 0; ri < rules.getLength(); ri++) {
CSSRule rule = rules.item(ri);
if (rule.getType() == CSSRule.MEDIA_RULE) {
CSSMediaRule mr = (CSSMediaRule) rule;
scanRules(mr.getCssRules(), classNameTest, ruleAction);
} else if (rule.getType() == CSSRule.STYLE_RULE) {
CSSStyleRuleImpl styleRule = (CSSStyleRuleImpl) rule;
SelectorList selectors = styleRule.getSelectors();
// if (!styleRule.getSelectorText().contains(".name"))
// continue; // selector text test might cause speed up...
for (int si = 0; si < selectors.getLength(); si++) {
Selector selector = selectors.item(si);
Set<String> classNames = classNamesInSelectorMatching(selector, classNameTest);
if (!classNames.isEmpty())
ruleAction.accept(classNames, styleRule);
}
}
}
}
// find matching class names in given (potentially complex) selector
Set<String> classNamesInSelectorMatching(Selector selector,
Predicate<String> nameMatches) {
switch (selector.getSelectorType()) {
case Selector.SAC_CHILD_SELECTOR:
case Selector.SAC_DESCENDANT_SELECTOR:
case Selector.SAC_DIRECT_ADJACENT_SELECTOR: {
DescendantSelector ds = (DescendantSelector) selector;
Set<String> set = new HashSet<>();
set.addAll(classNamesInSelectorMatching(ds.getAncestorSelector(), nameMatches));
set.addAll(classNamesInSelectorMatching(ds.getSimpleSelector(), nameMatches));
return set;
}
case Selector.SAC_NEGATIVE_SELECTOR: {
NegativeSelector ns = (NegativeSelector) selector;
return classNamesInSelectorMatching(ns.getSimpleSelector(), nameMatches);
}
case Selector.SAC_CONDITIONAL_SELECTOR: {
ConditionalSelector ns = (ConditionalSelector) selector;
return classNamesInConditionMatching(ns.getCondition(), nameMatches);
}
default:
return Collections.emptySet();
}
}
// find matching class names in given (potentially complex) condition
Set<String> classNamesInConditionMatching(Condition condition,
Predicate<String> nameMatches) {
switch (condition.getConditionType()) {
case Condition.SAC_CLASS_CONDITION: {
AttributeCondition ac = (AttributeCondition) condition;
if (nameMatches.test(ac.getValue()))
return Collections.singleton(ac.getValue());
else
return Collections.emptySet();
}
case Condition.SAC_AND_CONDITION:
case Condition.SAC_OR_CONDITION: {
CombinatorCondition cc = (CombinatorCondition) condition;
Set<String> set = new HashSet<>();
set.addAll(classNamesInConditionMatching(cc.getFirstCondition(), nameMatches));
set.addAll(classNamesInConditionMatching(cc.getSecondCondition(), nameMatches));
return set;
}
case Condition.SAC_NEGATIVE_CONDITION: {
NegativeCondition nc = (NegativeCondition) condition;
return classNamesInConditionMatching(nc.getCondition(), nameMatches);
}
default:
return Collections.emptySet();
}
}
I have tried it with input https://www.w3.org/2008/site/css/minimum-src.css and it seems to work for me.

Using Iterator with Java Selenium WebDriver

Using Selenium to gather text of all p elements within a specific div. I noticed while using List, Selenium scanned the whole DOM and stored empty text. So, I wanted to iterate through the DOM and only store values that are not equal to empty text via java.util.Iterator. Is this possible? Is there a more efficient way other than the List approach?
Iterator Approach:
public static boolean FeatureFunctionsCheck(String Feature){
try
{
Iterator<WebElement> all = (Iterator<WebElement>) Driver.Instance.findElement(By.xpath("//a[contains(text()," + Feature + ")]/ancestor::h3/following-sibling::div/div[#class='navMenu']/p"));
boolean check = false;
while(all.hasNext() && check){
WebElement temp = all.next();
if(!temp.getText().equals(""))
{
Log.Info("Functions: " + temp.getText());
all = (Iterator<WebElement>) Driver.Instance.findElement(By.xpath("//a[contains(text()," + Feature + ")]/ancestor::h3/following-sibling::div/div[#class='navMenu']/p"));
}
else
check = true;
}
return false;
}
catch(Exception e)
{
Log.Error("Failed()" + e);
return false;
}
}
Iterator Approach throws exception...
java.lang.ClassCastException: org.openqa.selenium.remote.RemoteWebElement cannot be cast to java.util.Iterator
List Approach Works, However Not Sure If This Is Efficient
public static boolean FeatureFunctionsCheck(String Feature){
try
{
List<WebElement> AllModelFunctions = new ArrayList<WebElement>();
Log.Info("[Test-235]: Selecting Feature");
for(WebElement element: AllModelFunctions){
if(!element.getText().equals(""))
{
Log.Info("Functions: " + element.getText());
}
}
return false;
}
catch(Exception e)
{
Log.Error("Failed()" + e);
return false;
}
}
findElement returns one WebElement. What you probably meant to do is to search for all elements with given xpath, using findElements:
Driver.Instance.findElements(...
Also the syntax is over-complicated. You can just get the list and iterate through it:
List<WebElement> elements = Driver.Instance.findElements(...);
for(WebElement element : elements) {
if(!element.getText().equals(""))
{
Log.Info("Functions: " + element.getText());
}
}
BTW I have to fully trust that Driver.Instance is an instance of the driver (typically in Java you don't have capitals for class instances, so I'm not sure if I understood it right). A more common syntax would be something like:
WebDriver driver = new FirefoxDriver(); // or another browser
driver.findElements(...);
// ...

Parsing XML with StAX with non-unique tag paths, design suggestions

I need to parse a large XML file (probably going to use StAX in Java) and output it into a delimited text file and I have a couple of design questions. First here is an example of the XML
<demographic>
<value>001</value>
<question>Name?</question>
<value>Bob</value>
<question>Last Name?</question>
<value>Smith</value>
<followUpQuestions>
<question>Middle Init.</question>
<value>J</value>
</followUpQuestions>
</demographic>
this would need to be outputted (in the delimited output file) as
001~Bob~Smith~J
so here are my questions:
How can I distinguish between all the different "value" tags, since the tag names are not unique. Currently I tried to resolve this by having 'state' variables that turn on once they pass question-text such as "Name?", however this approach doesnt really work for the first value since I have to check to make sure the 'name' and 'lastName' states are off to ensure I'm getting the first value.
Everytime the client changes the text of the questions (which happens) I have to change the code and recompile it. Is there anyway to avoid this? Maybe save the questions-text in a text file that the program reads in?
Can this be scalable? I need to extract over 100 values and the XML files are usually about 2 gigs large.
Thank you, in advance, for your help (from a Java and XML newbie)!!
UPDATE: here is my attempt to code the solution, can someone please help to streamline? There has to be a less messy way to do this:
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
class TestJavaForStackOverflow{
boolean nameState = false,
lastNameState = false,
middleInitState = false;
String name = "",
lastName = "",
middleInit = "",
value = "";
public void parse() throws IOException, XMLStreamException{
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader streamReader = factory.createXMLStreamReader(
new FileReader("/n04/data/revmgmt/anthony/scripts/Java_Programs/TestJavaForStackOverflow.xml"));
while(streamReader.hasNext()){
streamReader.next();
if(streamReader.getEventType() == XMLStreamReader.START_ELEMENT){
if("demographic".equals(streamReader.getLocalName())){
parseDemographicInformation(streamReader);
}
}
}
System.out.println(value + "~" + name + "~" + lastName + "~" + middleInit);
}
public void parseDemographicInformation(XMLStreamReader streamReader) throws XMLStreamException {
while(streamReader.hasNext()){
streamReader.next();
if(streamReader.getEventType() == XMLStreamReader.END_ELEMENT){
if("demographic".equals(streamReader.getLocalName())){
return;
}
}
else if(streamReader.getEventType() == XMLStreamReader.START_ELEMENT){
if("question".equals(streamReader.getLocalName())){
streamReader.next();
if("Name?".equals(streamReader.getText())){
nameState = true;
}
else if("Last Name?".equals(streamReader.getText())){
lastNameState = true;
}
else if("Middle Init.".equals(streamReader.getText())){
middleInitState = true;
}
}
else if("value".equals(streamReader.getLocalName())){
streamReader.next();
if(nameState){
name = streamReader.getText();
nameState = false;
}
else if (lastNameState){
lastName = streamReader.getText();
lastNameState = false;
}
else if (middleInitState){
middleInit = streamReader.getText();
middleInitState = false;
}
else {
value = streamReader.getText();
}
}
}
}
}
public static void main(String[] args){
TestJavaForStackOverflow t = new TestJavaForStackOverflow();
try{t.parse();}
catch(IOException e1){}
catch(XMLStreamException e2){}
}
}
I think the flags are not very scalable if you have a lot of different questions to parse, and neither are the global variables to hold the results... if you have 100 questions then you'll need 100 variables, and when they change over time it will be a bear to keep them up to date. I would use a map structure to hold the result, and another one to hold the correspondence between each question text and the corresponding field you are trying to capture (this is not actual Java, just an approximation):
public Map parseDemographicInformation(XmlStream xml, Map questionMap) {
Map record = new Map();
String field = "id";
while((elem = xml.getNextElement())) {
if(elem.tagName == "question") {
field = questionMap[elem.value];
} else if(elem.tagName == "value") {
record[field] = elem.value;
}
}
return record;
}
Then you have something like this to output the result:
String[] fieldsToOutput = { "id", "firstName", "lastName" }; // ideally read this from a file too so it can be changed dynamically
// ...
for(int i=0; i < fieldsToOutput.length; i++){
if(i > 0)
System.out.print("~");
System.out.print(record[fieldsToOutput[i]]);
}
System.out.println();

Categories

Resources