Как избежать "StaleElementReferenceException" в селене?
я реализую много тестов Селена с использованием Java. Иногда мои тесты терпят неудачу из-за StaleElementReferenceException
. Не могли бы вы предложить некоторые подходы к тому, чтобы сделать тесты более стабильными?
9 ответов:
Это может произойти, если операция 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; }
у меня была эта проблема периодически. Без моего ведома 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 обновляется, поэтому у вас есть недопустимая ссылка..
обойти это, сначала используя явное ожидание элемента, чтобы убедиться, что обновление завершено, а затем снова возьмите новую ссылку на элемент.
вот некоторый код psuedo для иллюстрации (адаптированный из некоторого кода 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();
Почему
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(); }
решающая часть-это "цепочка" собственного Селена
ExpectedConditions
черезExpectedConditions.refreshed()
. Это фактически ждет и проверяет, был ли элемент, о котором идет речь, обновлен во время указанного тайм-аута и дополнительно ждет, пока элемент станет кликабельный.посмотреть документация для обновленного метода.
решение в 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.
в моем проекте я ввел понятие 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
это работает для меня (100% работает) с помощью C#
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; }
возможно, он был добавлен совсем недавно, но другие ответы не упоминают неявную функцию ожидания Selenium, которая делает все вышеизложенное для вас и встроена в Selenium.
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
это повторится
findElement()
вызывает до тех пор, пока элемент не будет найден, или в течение 10 секунд.источник http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp