如何扩展Selenium By.class以创建更多灵活性?

如何扩展Selenium By.class以创建更多灵活性? 我查看了By.class ,我不太清楚如何处理这个问题。 看起来我需要创建一个接口类和一个静态By类,比如ByJavascriptGetWebElement ,以便创建这种包装器?

我希望能够称之为:

By.javascript("return document.querySelector(\"div#item div\");", el ); 

我也听说过一种更简单的方法,但我更喜欢用更传统的方式做到这一点:

 public By byJavascriptGetElement( WebElement we ) { return By. ??? } 

有任何想法吗?

这实际上可以轻松完成。

我们的想法是访问WebDriver实例并在其上运行JavaScript(如果它支持它)。 然后有很多validation,因为我们需要确保我们只返回我们承诺的内容。

ByJavaScript类本身将如下所示:

 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 findElements(SearchContext context) { JavascriptExecutor js = getJavascriptExecutorFromSearchContext(context); // call the JS, inspect and validate response Object response = js.executeScript(script); List 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 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)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 elements, WebElement ancestor) { for (Iterator 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 + "\""; } } 

此代码使用Google Guava库 。 它是Selenium的依赖项,因此您应该在类路径中使用它。 但是,如果有一些你不理解的东西,请看看番石榴。

需要考虑的事项:

  1. 记录整件事。
  2. 使用更好,更有帮助的例外。 考虑一些WebDriverException自定义子类。 还添加更多有用的消息和信息。
  3. 限制类对包的可见性。 或者将它嵌入到静态工厂中(如原始的By类中所示),以便它不能直接访问等。
  4. 写测试。 我已经尝试过最明显的用法(无法找到元素,从驱动程序搜索,从某些上下文搜索),一切似乎都没问题,但我没有广泛测试它。

用法:

 WebElement elem = driver.findElement(new ByJavaScript("return document.querySelector('.haha');")); 

现在,原始的By类是一个静态工厂,它给出了它自身的各种实现。 不幸的是,我们无法为其添加新的静态方法(不更改其源代码),因此我们将无法键入By.javascript("return something;") 。 我们必须创建自己的静态工厂来获得类似的东西:

 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); } } 

用法:

 WebElement elem = driver.findElement(MyBy.javascript("return document.querySelector('.haha');"));