如何扩展Selenium By.class以创建更多灵活性? [英] How can I extend the Selenium By.class to create more flexibility?

查看:158
本文介绍了如何扩展Selenium By.class以创建更多灵活性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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?

我希望能够这样称呼:

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.  ???
}

任何想法?

推荐答案

这实际上可以轻松完成。

This can actually be easily done.

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

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.

ByJavaScript class本身将如下所示:

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

}

此代码使用< a href =http://guava-libraries.googlecode.com/> Google Guava库。它是Selenium的依赖项,因此您应该在类路径中使用它。但如果你有什么不明白的地方,请看看番石榴。

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.

需要考虑的事项:


  1. 记录整件事。

  2. 使用更好,更有帮助的例外。考虑一些 WebDriverException 的自定义子类。还添加了更多有用的消息和信息。

  3. 限制类对包的可见性。或者将它嵌入到静态工厂中(如原始的 By 类中所示),以便它不能直接访问等。

  4. 写测试。我尝试过最明显的用法(无法找到元素,从驱动程序搜索,从某些上下文搜索),一切似乎都没问题,但我没有广泛测试它。

  1. Document the whole thing.
  2. Use better and more helpful exceptions. Consider some custom subclasses of WebDriverException. Also add more helpful messages and info.
  3. Restrict the class' visibility to package. Or embed it into a static factory (as seen in the original By class) so that it won't be accessible directly etc.
  4. Write tests. I have tried the most obvious usages (element can't be found, search from driver, search from some context) and everything seems to be ok, but I didn't test it extensively.

用法:

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

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

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

用法:

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

这篇关于如何扩展Selenium By.class以创建更多灵活性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆