7

Selenium By.classを拡張して柔軟性を高めるにはどうすればよいですか? By.classを見ましたが、これにアプローチする方法がよくわかりません。この種のラッパーを作成するには、インターフェイス クラスとByJavascriptGetWebElementなどの静的な By クラスを作成する必要があるように見えます。

私はそれを次のように呼び出すことができるようにしたいと思います:

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

また、より簡単な方法についても聞いたことがありますが、より伝統的な方法で行うことをお勧めします。

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

何か案は?

4

1 に答える 1

10

これは実際には簡単に行うことができます。

アイデアは、インスタンスへのアクセスを取得し、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 + "\"";
    }

}

このコードはGoogle Guava ライブラリを使用しています。これは Selenium の依存関係であるため、クラスパスに含める必要があります。でも、わからないことがあれば Guava を調べてみてください。

考慮事項:

  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');"));
于 2013-10-18T23:30:10.520 に答える