How can I extend the Selenium By.class to create more flexibility? I looked at the By.class and I don't quite know how to approach this. It looks like I need to create an interface class and a static By class, such as ByJavascriptGetWebElement, in order to create this kind of wrapper?
I would like to be able to call it like:
By.javascript("return document.querySelector(\"div#item div\");", el );
And I have also heard of an easier way to do it but I would prefer to do it the more traditional way:
public By byJavascriptGetElement( WebElement we ) {
return By. ???
}
Any ideas?
public class T3EmployeeMaintenance extends Setup{ //public static WebDriver driver = new FirefoxDriver(); //no need of this now @BeforeClass public void invokeBrowser() { //... Show activity on this post. You have to finish class at the end. in your html simply add at the end of each class you declared.
Initializes a new instance of the By class using the given functions to find elements. Gets or sets the value of the description for this By class instance. Gets or sets the method used to find a single element matching specified criteria. Gets or sets the method used to find all elements matching specified criteria.
SearchContext is the super most interface in selenium, which is extended by another interface called WebDriver. All the abstract methods of SearchContext and WebDriver interfaces are implemented in RemoteWebDriver class.
This can actually be easily done.
The idea is to get access to the WebDriver
instance and run the JavaScript on it (if it supports it). Then there's a lot of validating, because we need to make sure we only return what we promised.
The ByJavaScript
class itself will look like this:
public class ByJavaScript extends By implements Serializable {
private final String script;
public ByJavaScript(String script) {
checkNotNull(script, "Cannot find elements with a null JavaScript expression.");
this.script = script;
}
@Override
public List<WebElement> findElements(SearchContext context) {
JavascriptExecutor js = getJavascriptExecutorFromSearchContext(context);
// call the JS, inspect and validate response
Object response = js.executeScript(script);
List<WebElement> elements = getElementListFromJsResponse(response);
// filter out the elements that aren't descendants of the context node
if (context instanceof WebElement) {
filterOutElementsWithoutCommonAncestor(elements, (WebElement)context);
}
return elements;
}
private static JavascriptExecutor getJavascriptExecutorFromSearchContext(SearchContext context) {
if (context instanceof JavascriptExecutor) {
// context is most likely the whole WebDriver
return (JavascriptExecutor)context;
}
if (context instanceof WrapsDriver) {
// context is most likely some WebElement
WebDriver driver = ((WrapsDriver)context).getWrappedDriver();
checkState(driver instanceof JavascriptExecutor, "This WebDriver doesn't support JavaScript.");
return (JavascriptExecutor)driver;
}
throw new IllegalStateException("We can't invoke JavaScript from this context.");
}
@SuppressWarnings("unchecked") // cast thoroughly checked
private static List<WebElement> getElementListFromJsResponse(Object response) {
if (response == null) {
// no element found
return Lists.newArrayList();
}
if (response instanceof WebElement) {
// a single element found
return Lists.newArrayList((WebElement)response);
}
if (response instanceof List) {
// found multiple things, check whether every one of them is a WebElement
checkArgument(
Iterables.all((List<?>)response, Predicates.instanceOf(WebElement.class)),
"The JavaScript query returned something that isn't a WebElement.");
return (List<WebElement>)response; // cast is checked as far as we can tell
}
throw new IllegalArgumentException("The JavaScript query returned something that isn't a WebElement.");
}
private static void filterOutElementsWithoutCommonAncestor(List<WebElement> elements, WebElement ancestor) {
for (Iterator<WebElement> iter = elements.iterator(); iter.hasNext(); ) {
WebElement elem = iter.next();
// iterate over ancestors
while (!elem.equals(ancestor) && !elem.getTagName().equals("html")) {
elem = elem.findElement(By.xpath("./.."));
}
if (!elem.equals(ancestor)) {
iter.remove();
}
}
}
@Override
public String toString() {
return "By.javaScript: \"" + script + "\"";
}
}
This code uses the Google Guava library. It is a dependency of Selenium, so you should have it on your classpath. But if there's something you don't understand, look into Guava.
Things to consider:
WebDriverException
. Also add more helpful messages and info.By
class) so that it won't be accessible directly etc.Usage:
WebElement elem = driver.findElement(new ByJavaScript("return document.querySelector('.haha');"));
Now, the original By
class is a static factory that gives out various implementations of itself. Unfortunately, we can't add a new static method to it (without changing its source), so we won't be able to type By.javascript("return something;")
. We have to create our own static factory to get something similar:
public class MyBy {
/**
* Returns a {@code By} which locates elements by the JavaScript expression passed to it.
*
* @param script The JavaScript expression to run and whose result to return
*/
public static By javascript(String script) {
return new ByJavaScript(script);
}
}
Usage:
WebElement elem = driver.findElement(MyBy.javascript("return document.querySelector('.haha');"));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With