Я использую много тестов Selenium с использованием Java. Иногда мои тесты терпят неудачу из-за StaleElementReferenceException
. Не могли бы вы предложить некоторые подходы к повышению стабильности тестов?
Как избежать исключения StaleElementReferenceException в Selenium?
Ответ 1
Это может произойти, если операция DOM, происходящая на странице, временно делает элемент недоступным. Чтобы учесть эти случаи, вы можете попытаться получить доступ к элементу несколько раз в цикле, прежде чем, наконец, вызвать исключение.
Попробуйте это отличное решение от darrelgrainger.blogspot.com:
public boolean retryingFindClick(By by) {
boolean result = false;
int attempts = 0;
while(attempts < 2) {
try {
driver.findElement(by).click();
result = true;
break;
} catch(StaleElementException e) {
}
attempts++;
}
return result;
}
Ответ 2
У меня возникала эта проблема с перерывами. Без ведома меня BackboneJS запускался на странице и заменял элемент, который я пытался щелкнуть. Мой код выглядел следующим образом.
driver.findElement(By.id("checkoutLink")).click();
Что, конечно, функционально совпадает с этим.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();
Иногда случалось, что javascript заменил бы элемент checkoutLink между поиском и щелчком, т.е.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();
Это по праву привело к исключению StaleElementReferenceException при попытке щелкнуть ссылку. Я не мог найти надежный способ сказать WebDriver подождать, пока javascript не закончит работу, поэтому вот как я в итоге решил его.
new WebDriverWait(driver, timeout)
.ignoring(StaleElementReferenceException.class)
.until(new Predicate<WebDriver>() {
@Override
public boolean apply(@Nullable WebDriver driver) {
driver.findElement(By.id("checkoutLink")).click();
return true;
}
});
Этот код будет постоянно пытаться щелкнуть ссылку, игнорируя значения StaleElementReferenceExceptions до тех пор, пока не будет достигнут клик или не будет достигнут тайм-аут. Мне нравится это решение, потому что это избавляет вас от необходимости писать логику повтора и использует только встроенные конструкции WebDriver.
Ответ 3
Как правило, это связано с обновлением DOM, и вы пытаетесь получить доступ к обновленному/новому элементу, но DOM обновился, так что у вас есть неверная ссылка.
Обходите это, сначала используя явное ожидание элемента, чтобы убедиться, что обновление завершено, затем снова получите новую ссылку на элемент.
Здесь приведен какой-то код psuedo для иллюстрации (адаптирован из некоторого кода С#, который я использую для ТОЧНО):
WebDriverWait wait = new WebDriverWait(browser, TimeSpan.FromSeconds(10));
IWebElement aRow = browser.FindElement(By.XPath(SOME XPATH HERE);
IWebElement editLink = aRow.FindElement(By.LinkText("Edit"));
//this Click causes an AJAX call
editLink.Click();
//must first wait for the call to complete
wait.Until(ExpectedConditions.ElementExists(By.XPath(SOME XPATH HERE));
//you've lost the reference to the row; you must grab it again.
aRow = browser.FindElement(By.XPath(SOME XPATH HERE);
//now proceed with asserts or other actions.
Надеюсь, это поможет!
Ответ 4
Решение Кенни хорошо, но оно может быть написано более элегантно
new WebDriverWait(driver, timeout)
.ignoring(StaleElementReferenceException.class)
.until((WebDriver d) -> {
d.findElement(By.id("checkoutLink")).click();
return true;
});
Или также:
new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();
Но в любом случае, лучшее решение - это полагаться на библиотеку Selenide, которая обрабатывает подобные вещи и многое другое. (вместо ссылок на элементы он обрабатывает прокси, поэтому вам никогда не придется иметь дело с устаревшими элементами, что может быть довольно сложно). селенида
Ответ 5
Причина возникновения StaleElementReferenceException
уже была изложена: обновления DOM между поиском и выполнением чего-либо с элементом.
Для проблемы щелчка я недавно использовал решение, подобное этому:
public void clickOn(By locator, WebDriver driver, int timeout)
{
final WebDriverWait wait = new WebDriverWait(driver, timeout);
wait.until(ExpectedConditions.refreshed(
ExpectedConditions.elementToBeClickable(locator)));
driver.findElement(locator).click();
}
Важнейшей частью является "сцепление" собственного Selenium ExpectedConditions
через ExpectedConditions.refreshed()
. Это фактически ожидает и проверяет, был ли соответствующий элемент обновлен в течение указанного времени ожидания, и дополнительно ожидает, что элемент станет активным.
Посмотрите документацию для обновленного метода.
Ответ 6
В моем проекте я представил понятие StableWebElement. Это оболочка для WebElement, которая способна определить, является ли элемент Stale и найти новую ссылку на исходный элемент. Я добавил вспомогательные методы для поиска элементов, которые возвращают StableWebElement вместо WebElement, и проблема с StaleElementReference исчезла.
public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
var element = context.FindElement(by);
return new StableWebElement(context, element, by, SearchApproachType.First);
}
Код на С# доступен на моей странице проекта, но его можно легко портировать в java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs
Ответ 7
Решение в С# было бы:
Класс помощника:
internal class DriverHelper
{
private IWebDriver Driver { get; set; }
private WebDriverWait Wait { get; set; }
public DriverHelper(string driverUrl, int timeoutInSeconds)
{
Driver = new ChromeDriver();
Driver.Url = driverUrl;
Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeoutInSeconds));
}
internal bool ClickElement(string cssSelector)
{
//Find the element
IWebElement element = Wait.Until(d=>ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver);
return Wait.Until(c => ClickElement(element, cssSelector));
}
private bool ClickElement(IWebElement element, string cssSelector)
{
try
{
//Check if element is still included in the dom
//If the element has changed a the OpenQA.Selenium.StaleElementReferenceException is thrown.
bool isDisplayed = element.Displayed;
element.Click();
return true;
}
catch (StaleElementReferenceException)
{
//wait until the element is visible again
element = Wait.Until(d => ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver);
return ClickElement(element, cssSelector);
}
catch (Exception)
{
return false;
}
}
}
Призвание:
DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10);
driverHelper.ClickElement("input[value='csharp']:first-child");
Аналогично может использоваться для Java.
Ответ 8
Решение Kenny устарело, используйте это, я использую класс действий для двойного щелчка, но вы можете делать все что угодно.
new FluentWait<>(driver).withTimeout(30, TimeUnit.SECONDS).pollingEvery(5, TimeUnit.SECONDS)
.ignoring(StaleElementReferenceException.class)
.until(new Function() {
@Override
public Object apply(Object arg0) {
WebElement e = driver.findelement(By.xpath(locatorKey));
Actions action = new Actions(driver);
action.moveToElement(e).doubleClick().perform();
return true;
}
});
Ответ 9
Это работает для меня (работает на 100%) с использованием С#
public Boolean RetryingFindClick(IWebElement webElement)
{
Boolean result = false;
int attempts = 0;
while (attempts < 2)
{
try
{
webElement.Click();
result = true;
break;
}
catch (StaleElementReferenceException e)
{
Logging.Text(e.Message);
}
attempts++;
}
return result;
}
Ответ 10
Проблема в том, что когда вы передаете элемент из Javascript в Java обратно в Javascript, он может покинуть DOM.
Попробуйте сделать все это в Javascript:
driver.executeScript("document.querySelector('#my_id').click()")
Ответ 11
Попробуйте это
while (true) { // loops forever until break
try { // checks code for exceptions
WebElement ele=
(WebElement)wait.until(ExpectedConditions.elementToBeClickable((By.xpath(Xpath))));
break; // if no exceptions breaks out of loop
}
catch (org.openqa.selenium.StaleElementReferenceException e1) {
Thread.sleep(3000); // you can set your value here maybe 2 secs
continue; // continues to loop if exception is found
}
}
Ответ 12
Я нашел решение здесь. В моем случае элемент становится недоступным, если оставить текущее окно, вкладку или страницу и вернуться снова.
.ignoring(StaleElement...),.refreshed(...) и elementToBeClicable (...) не помогли, и я получил исключение для строки act.doubleClick(element).build().perform();
.
Использование функции в моем основном тестовом классе:
openForm(someXpath);
Моя функция BaseTest:
int defaultTime = 15;
boolean openForm(String myXpath) throws Exception {
int count = 0;
boolean clicked = false;
while (count < 4 || !clicked) {
try {
WebElement element = getWebElClickable(myXpath,defaultTime);
act.doubleClick(element).build().perform();
clicked = true;
print("Element have been clicked!");
break;
} catch (StaleElementReferenceException sere) {
sere.toString();
print("Trying to recover from: "+sere.getMessage());
count=count+1;
}
}
Моя функция BaseClass:
protected WebElement getWebElClickable(String xpath, int waitSeconds) {
wait = new WebDriverWait(driver, waitSeconds);
return wait.ignoring(StaleElementReferenceException.class).until(
ExpectedConditions.refreshed(ExpectedConditions.elementToBeClickable(By.xpath(xpath))));
}
Ответ 13
Возможна потенциальная проблема, которая приводит к исключению StaleElementReferenceException, о котором никто до сих пор не упомянул (в отношении действий).
Я объясняю это в Javascript, но то же самое в Java.
Это не сработает:
let actions = driver.actions({ bridge: true })
let a = await driver.findElement(By.css('#a'))
await actions.click(a).perform() // this leads to a DOM change, #b will be removed and added again to the DOM.
let b = await driver.findElement(By.css('#b'))
await actions.click(b).perform()
Но создание экземпляров действий снова решит это:
let actions = driver.actions({ bridge: true })
let a = await driver.findElement(By.css('#a'))
await actions.click(a).perform() // this leads to a DOM change, #b will be removed and added again to the DOM.
actions = driver.actions({ bridge: true }) // new
let b = await driver.findElement(By.css('#b'))
await actions.click(b).perform()
Ответ 14
Возможно, это было добавлено совсем недавно, но в других ответах не упоминается функция неявного ожидания Selenium, которая делает все вышеперечисленное для вас и встроена в Selenium.
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
Это приведет к повторному вызову findElement()
до тех пор, пока элемент не будет найден, или в течение 10 секунд.
Источник - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp