これは実際には簡単に行うことができます。
アイデアは、インスタンスへのアクセスを取得し、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 を調べてみてください。
考慮事項:
- 全体を文書化します。
- より適切で役立つ例外を使用します。のいくつかのカスタム サブクラスを検討してください
WebDriverException
。また、役立つメッセージや情報を追加します。
- クラスの可視性をパッケージに制限します。または、(元のクラスに見られるように) 静的ファクトリに埋め込んで、
By
直接アクセスできないようにします。
- テストを書きます。私は最も明白な使用法 (要素が見つからない、ドライバーから検索する、コンテキストから検索する) を試しましたが、すべて問題ないように見えますが、広範囲にテストしませんでした。
使用法:
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');"));