package com.ttdev.wicketpagetest;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebElement;
/**
* It is a Wicket locator for Selenium. For example, Using //myform[2]//name as
* the path it will first locate the 3rd element (breadth-first search) with
* wicket:id="myform" and then the first element in it with wicket:id="name". If
* it must be an immediate child, use / instead of //.
*
* @author Andy Chu
* @author Kent Tong
*/
public class ByWicketIdPathFastVersion extends By {
private String path;
private Pattern stepPattern;
public ByWicketIdPathFastVersion(String path) {
this.path = path;
this.stepPattern = Pattern.compile("(//?)([\\w\\.]+)(\\[(\\d+)\\])?");
}
@Override
public List<WebElement> findElements(SearchContext context) {
WebElement element = findByPath(context, path);
return element == null ? null : Arrays
.asList(new WebElement[] { element });
}
private WicketWebElement getRootElement(SearchContext context,
String wicketId) {
List<WebElement> elements = context
.findElements(By
.xpath("//*["
+ "(@wicketpath = '"
+ wicketId
+ "')"
+ " or "
+ "(starts-with(@wicketpath, '"
+ wicketId
+ "_'))"
+ " or "
+ "(contains(@wicketpath, '_"
+ wicketId
+ "_'))"
+ " or "
+ "('_"
+ wicketId
+ "' = substring(@wicketpath, string-length(@wicketpath)- string-length('_"
+ wicketId + "') +1))" + "]"));
WicketWebElement root = new WicketWebElement();
WicketWebElement last = root;
for (WebElement element : elements) {
try {
WicketWebElement node = new WicketWebElement(element);
last.findParent(node).add(node);
last = node;
} catch (StaleElementReferenceException e) {
// NOTE: just ignore this stale element
}
}
return root;
}
public WebElement findByPath(SearchContext context, String path) {
Matcher matcher = stepPattern.matcher(path);
WicketWebElement baseElement = null;
while (matcher.find()) {
boolean isAnyLevelDeep = matcher.group(1).equals("//");
String wicketId = matcher.group(2);
String indexString = matcher.group(4);
int index = 0;
if (indexString != null) {
index = Integer.parseInt(indexString);
}
boolean hasNoRootYet = (baseElement == null);
if (hasNoRootYet) {
baseElement = getRootElement(context, wicketId);
}
WicketWebElement child = findByWicketId(baseElement,
isAnyLevelDeep, wicketId, index);
if (child == null) {
return null;
}
baseElement = child;
}
return baseElement == null ? null : baseElement.getElement();
}
private WicketWebElement findByWicketId(WicketWebElement baseElement,
boolean isAnyLevelDeep, String wicketId, int index) {
int noElementsFound = 0;
Queue<WicketWebElement> queue = new ArrayDeque<WicketWebElement>();
queue.addAll(baseElement.getChildren());
while (!queue.isEmpty()) {
WicketWebElement child = queue.poll();
if (matchesWicketId(child.getElement(), wicketId)) {
if (noElementsFound == index) {
return child;
}
noElementsFound++;
}
if (isAnyLevelDeep) {
queue.addAll(child.getChildren());
}
}
return null;
}
public boolean matchesWicketId(WebElement element, String wicketId) {
try {
String wicketIdValue = element.getAttribute("wicket:id");
if (wicketIdValue != null && wicketIdValue.equals(wicketId)) {
return true;
}
// wicket:id may not be there (see WICKET-2832), also try
// wicketpath.
// It must be prepared to deal with wicketpath values like ???_foo
// and
// ???_foo_0.
// In addition, if the wicket ID contains a dot, it must be escaped.
String wicketPathPattern = "^((.*[^_])_)*"
+ wicketId.replace(".", "\\.").replace("_", "__")
+ "(_\\d+)?$";
String pathValue = element.getAttribute("wicketpath");
if (pathValue != null && pathValue.matches(wicketPathPattern)) {
return true;
}
} catch (StaleElementReferenceException e) {
return false;
}
return false;
}
@Override
public String toString() {
return getClass().getSimpleName() + ": " + path;
}
}