Я реализую множество тестов Selenium с использованием Java. Иногда мои тесты терпят неудачу из-за файла StaleElementReferenceException
. Не могли бы вы предложить какие-нибудь подходы к повышению стабильности тестов?
java
selenium-webdriver
Hoang Nguyen
источник
источник
У меня периодически возникала эта проблема. Без моего ведома 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.
источник
Обычно это происходит из-за того, что DOM обновляется, и вы пытаетесь получить доступ к обновленному / новому элементу, но DOM обновился, поэтому у вас есть недопустимая ссылка.
Чтобы обойти это, сначала используйте явное ожидание элемента, чтобы убедиться, что обновление завершено, а затем снова возьмите новую ссылку на элемент.
Вот некоторый псевдо-код для иллюстрации (адаптированный из некоторого кода C #, который я использую ТОЧНО для этой проблемы):
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.
Надеюсь это поможет!
источник
Решение Кенни хорошее, но его можно написать более элегантно
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, она справляется с подобными и другими вещами. (вместо ссылок на элементы он обрабатывает прокси, поэтому вам никогда не придется иметь дело с устаревшими элементами, что может быть довольно сложно). Селенид
источник
Причина, по которой это
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()
. Это фактически ожидает и проверяет, обновился ли рассматриваемый элемент в течение указанного тайм-аута, а также ожидает, когда элемент станет интерактивным.Посмотрите документацию по обновленному методу .
источник
В своем проекте я ввел понятие StableWebElement. Это оболочка для WebElement, которая может определять, является ли элемент устаревшим, и находить новую ссылку на исходный элемент. Я добавил вспомогательные методы для поиска элементов, которые возвращают 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); }
Код на C # доступен на странице моего проекта, но его можно легко перенести на java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs
источник
Решение на C #:
Класс помощника:
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.
источник
Решение Кенни устарело, используйте это, я использую класс действий для двойного щелчка, но вы можете делать что угодно.
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; } });
источник
Чистый
findByAndroidId
метод, который изящно обрабатываетStaleElementReference
.Это в значительной степени основано на ответе jspcal, но мне пришлось изменить этот ответ, чтобы он работал чисто с нашей настройкой, и поэтому я хотел добавить его здесь, если он будет полезен другим. Если этот ответ вам помог, пожалуйста, проголосуйте за ответ jspcal .
// This loops gracefully handles StateElementReference errors and retries up to 10 times. These can occur when an element, like a modal or notification, is no longer available. export async function findByAndroidId( id, { assert = wd.asserters.isDisplayed, timeout = 10000, interval = 100 } = {} ) { MAX_ATTEMPTS = 10; let attempt = 0; while( attempt < MAX_ATTEMPTS ) { try { return await this.waitForElementById( `android:id/${ id }`, assert, timeout, interval ); } catch ( error ) { if ( error.message.includes( "StaleElementReference" ) ) attempt++; else throw error; // Re-throws the error so the test fails as normal if the assertion fails. } } }
источник
Это работает для меня (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; }
источник
Проблема в том, что к тому времени, когда вы передадите элемент из Javascript в Java обратно в Javascript, он может покинуть DOM.
Попробуйте сделать все на Javascript:
driver.executeScript("document.querySelector('#my_id').click()")
источник
Попробуй это
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 } }
источник
Я нашел здесь решение . В моем случае элемент становится недоступным в случае выхода из текущего окна, вкладки или страницы и возврата обратно.
.ignoring (StaleElement ...), .refreshed (...) и elementToBeClicable (...) не помогли, и я получал исключение в
act.doubleClick(element).build().perform();
строке.Использование функции в моем основном тестовом классе:
Моя функция 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)))); }
источник
Возможна потенциальная проблема, которая приводит к исключению 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()
источник
Обычно StaleElementReferenceException, когда элемент, к которому мы пытаемся получить доступ, появился, но другие элементы могут повлиять на положение интересующего нас элемента, поэтому, когда мы пытаемся щелкнуть или получить текст, или попытаться выполнить какое-либо действие с WebElement, мы получаем исключение, которое обычно говорит, что элемент не прикреплен к DOM .
Решение, которое я пробовал, выглядит следующим образом:
protected void clickOnElement(By by) { try { waitForElementToBeClickableBy(by).click(); } catch (StaleElementReferenceException e) { for (int attempts = 1; attempts < 100; attempts++) { try { waitFor(500); logger.info("Stale element found retrying:" + attempts); waitForElementToBeClickableBy(by).click(); break; } catch (StaleElementReferenceException e1) { logger.info("Stale element found retrying:" + attempts); } } } protected WebElement waitForElementToBeClickableBy(By by) { WebDriverWait wait = new WebDriverWait(getDriver(), 10); return wait.until(ExpectedConditions.elementToBeClickable(by)); }
В приведенном выше коде я сначала пытаюсь подождать, а затем щелкнуть элемент, если возникает исключение, затем я ловлю его и пытаюсь зациклить его, поскольку есть вероятность, что все элементы могут не быть загружены, и снова может возникнуть исключение.
источник
Возможно, он был добавлен совсем недавно, но в других ответах не упоминается функция неявного ожидания Selenium, которая выполняет все вышеперечисленное за вас и встроена в Selenium.
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
Это повторит попытку
findElement()
вызовы, пока элемент не будет найден, или в течение 10 секунд.Источник - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp
источник