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

dja*_*fan 5 java selenium webdriver selenium-webdriver

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

我想能够称之为:

By.javascript("return document.querySelector(\"div#item div\");", el );
Run Code Online (Sandbox Code Playgroud)

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

public By byJavascriptGetElement( WebElement we ) {
    return By.  ???
}
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

Pet*_*ček 9

这实际上可以轻松完成.

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

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<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 + "\"";
    }

}
Run Code Online (Sandbox Code Playgroud)

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

需要考虑的事项:

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

用法:

WebElement elem = driver.findElement(new ByJavaScript("return document.querySelector('.haha');"));
Run Code Online (Sandbox Code Playgroud)

现在,原始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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

WebElement elem = driver.findElement(MyBy.javascript("return document.querySelector('.haha');"));
Run Code Online (Sandbox Code Playgroud)

  • 谢谢Slanec.这就是我一直在寻找的答案.你的答案中有很多东西是有道理的,但如果不花费很多时间我就不会想到它.我很感激你的回答. (2认同)