Краткий ответ: используйте #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
, поскольку они могут только сказать вам, существует ли элемент , а не если он действительно видим . ОП не определил, что означает visible , но обычно влечет за собой
- имеет
opacity
> 0
- свойство
display
имеет значение, отличное от none
-
visibility
реквизит установлен на visible
- другие элементы не скрывают его (это самый верхний элемент)
Большинство людей также включают требование, чтобы оно действительно находилось внутри области просмотра (чтобы человек мог его видеть).
По какой-то причине эта вполне нормальная потребность не удовлетворяется чистым Java API, в то время как внешние интерфейсы Selenium, основанные на нем, часто реализуют некоторые вариации isVisible
, поэтому я знал, что это должно быть возможно. И после просмотра источника Node framework WebDriver.IO я нашел источник из isVisible
, который теперь переименован в более удачное имя isVisibleInViewport
в 5.0- бета.
По сути, они реализуют пользовательскую команду как вызов, который делегирует 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 .
Если вы не выполняли JS с использованием Selenium до того, как могли взглянуть на этот или просмотреть JavaScriptExecutor API .
Обычно стараются всегда использовать неблокирующие асинхронные скрипты (что означает # executeAsyncScript ), но, поскольку у нас уже есть синхронный блокирующий скрипт, мы могли бы также использовать обычный вызов синхронизации. Возвращаемый объект может иметь много типов Object, поэтому приведение происходит соответствующим образом. Это может быть один из способов сделать это:
/**
* 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']"));
}
}