简答:使用#visibilityOfElementLocated
使用isDisplayed 或类似名称的答案均不正确。他们只检查display 属性是否不是none,而不检查元素是否真的可以看到! Selenium 在ExpectedConditions 类中添加了一堆静态实用程序方法。在这种情况下可以使用其中两个:
用法
@Test
// visibilityOfElementLocated has been statically imported
public demo(){
By searchButtonSelector = By.className("search_button");
WebDriverWait wait = new WebDriverWait(driver, 10);
driver.get(homeUrl);
WebElement searchButton = wait.until(
visibilityOfElementLocated
(searchButtonSelector));
//clicks the search button
searchButton.click();
在客户端上运行的自定义可见性检查
这是我在了解ExpectedConditions 上的实用方法之前的回答。它可能仍然是相关的,因为我认为它比上面提到的方法做得更多,它只检查元素的高度和宽度。
本质上:这不能由 Java 和 findElementBy* 方法和 WebElement#isDisplayed 单独回答,因为它们只能告诉你一个元素是否存在 ,而不是它是否真的存在 可见。 OP 没有定义 visible 的含义,但它通常需要
- 它有一个
opacity > 0
- 它的
display 属性设置为none 以外的其他值
-
visibility 属性设置为 visible
- 没有其他元素隐藏它(它是最顶层的元素)
大多数人还会要求它实际上也在视口内(这样人们就能看到它)。
出于某种原因,纯 Java API 无法满足这种非常正常的需求,而基于它的 Selenium 前端通常会实现 isVisible 的一些变体,这就是为什么我知道这应该是可能的。在浏览了 Node 框架 WebDriver.IO 的源代码后,我发现了 isVisible 的 the source,现在在 5.0-beta 中更贴切地改名为 isVisibleInViewport。 p>
基本上,他们将自定义命令实现为一个调用,委托给在客户端运行的 javascript 并执行实际工作!这是“服务器”位:
export default function isDisplayedInViewport () {
return getBrowserObject(this).execute(isDisplayedInViewportScript, {
[ELEMENT_KEY]: this.elementId, // w3c compatible
ELEMENT: this.elementId // jsonwp compatible
})
}
所以有趣的是发送到客户端运行的 javascript:
/**
* check if element is visible and within the viewport
* @param {HTMLElement} elem element to check
* @return {Boolean} true if element is within viewport
*/
export default function isDisplayedInViewport (elem) {
const dde = document.documentElement
let isWithinViewport = true
while (elem.parentNode && elem.parentNode.getBoundingClientRect) {
const elemDimension = elem.getBoundingClientRect()
const elemComputedStyle = window.getComputedStyle(elem)
const viewportDimension = {
width: dde.clientWidth,
height: dde.clientHeight
}
isWithinViewport = isWithinViewport &&
(elemComputedStyle.display !== 'none' &&
elemComputedStyle.visibility === 'visible' &&
parseFloat(elemComputedStyle.opacity, 10) > 0 &&
elemDimension.bottom > 0 &&
elemDimension.right > 0 &&
elemDimension.top < viewportDimension.height &&
elemDimension.left < viewportDimension.width)
elem = elem.parentNode
}
return isWithinViewport
}
这段 JS 实际上可以(几乎)逐字复制到您自己的代码库中(删除 export default 并将 const 替换为 var 在非常青浏览器的情况下)!要使用它,请将它从 File 读入一个 String,它可以由 Selenium 发送以在客户端上运行。
另一个可能值得研究的有趣且相关的脚本是selectByVisibleText。
如果您之前没有使用 Selenium 执行过 JS,您可以拥有 a small peek into this 或浏览 JavaScriptExecutor API。
通常,尝试始终使用非阻塞异步脚本(意思是#executeAsyncScript),但由于我们已经有一个同步的阻塞脚本,我们不妨使用正常的同步调用。返回的对象可以是多种类型的对象,因此请适当地进行转换。这可能是一种方法:
/**
* Demo of a java version of webdriverio's isDisplayedInViewport
* https://github.com/webdriverio/webdriverio/blob/v5.0.0-beta.2/packages/webdriverio/src/commands/element/isDisplayedInViewport.js
* The super class GuiTest just deals with setup of the driver and such
*/
class VisibleDemoTest extends GuiTest {
public static String readScript(String name) {
try {
File f = new File("selenium-scripts/" + name + ".js");
BufferedReader reader = new BufferedReader( new FileReader( file ) );
return reader.lines().collect(Collectors.joining(System.lineSeparator()));
} catch(IOError e){
throw new RuntimeError("No such Selenium script: " + f.getAbsolutePath());
}
}
public static Boolean isVisibleInViewport(RemoteElement e){
// according to the Webdriver spec a string that identifies an element
// should be deserialized into the corresponding web element,
// meaning the 'isDisplayedInViewport' function should receive the element,
// not just the string we passed to it originally - how this is done is not our concern
//
// This is probably when ELEMENT and ELEMENT_KEY refers to in the wd.io implementation
//
// Ref https://w3c.github.io/webdriver/#dfn-json-deserialize
return js.executeScript(readScript("isDisplayedInViewport"), e.getId());
}
public static Boolean isVisibleInViewport(String xPath){
driver().findElementByXPath("//button[@id='should_be_visible']");
}
@Test
public demo_isVisibleInViewport(){
// you can build all kinds of abstractions on top of the base method
// to make it more Selenium-ish using retries with timeouts, etc
assertTrue(isVisibleInViewport("//button[@id='should_be_visible']"));
assertFalse(isVisibleInViewport("//button[@id='should_be_hidden']"));
}
}