Як уникнути “StaleElementReferenceException” у селені?


85

Я впроваджую багато тестів Selenium за допомогою Java. Іноді мої тести провалюються через StaleElementReferenceException. Чи не могли б ви запропонувати кілька підходів, щоб зробити тести більш стабільними?

Відповіді:


89

Це може статися, якщо операція 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;
}

1
Оце Так! Це було якраз те, що мені потрібно. Дякую!
SpartaSixZero

1
Це можна також виправити, використовуючи різні посилання на елемент.
Ріпон Аль-Васім

@jspcal, це спрацювало для мене як шарм! Дуже дякую!
Ентоні Окот,

Якщо вищевказане не вирішує проблему, оновлення до останнього хромедрівера вирішило це для нас.
Vdex

5
Це жахливо у багатьох відношеннях. Це справді вирішує мою поточну проблему, тож дякую.
Інженер-програміст

66

Я мав це питання з перервами. Невідомо для мене, 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.


ця відповідь застаріла.
vaibhavcool20

19

Як правило, це пов’язано з тим, що 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.

Сподіваюся, це допомагає!


14

Рішення Кенні хороше, проте його можна написати більш елегантно

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, вона обробляє подібні речі та багато іншого. (замість посилань на елементи він обробляє проксі-сервери, тому вам ніколи не доведеться мати справу з застарілими елементами, що може бути досить складно). Селенід


Застереження: я просто щасливий користувач селеніду, нічого спільного з його розробкою
cocorossello

Ваше друге рішення буде працювати, оскільки елемент перестає працювати, коли ви клацаєте по ньому, а не коли знаходите його.
Раджагопалан,

Використовуйте селенід, щоб уникнути цієї проблеми, набагато простіше. Селен не призначений для використання окремо через цю проблему та той факт, що API низького рівня для простого користувача
cocorossello

Я це прекрасно усвідомлюю. Я використовую WATIR, який є обгорткою навколо Ruby Selenium Binding, WATIR автоматично усуває всі ці проблеми (для застарілого елемента). Я шукаю щось еквівалентне прив'язуванні Java, я знайшов Selenide, але не знаю, як змінити неявне очікування та явне очікування в selenide. Чи можете ви сказати мені, як це зробити? Або ви можете запропонувати мені будь-який матеріал, де я можу звернутися? і яка ваша думка про FluentLenium?
Раджагопалан

1
Люди повинні знати, що вибрана відповідь ОП датується 2012 роком. Багато речей змінилося за останні 7 років. Ця відповідь є більш правильною для 2019 року.
hfontanez

10

Причина того, що 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(). Це фактично чекає та перевіряє, чи оновлювався відповідний елемент протягом зазначеного тайм-ауту, і додатково чекає, коли елемент стане інтерактивним.

Погляньте на документацію щодо оновленого методу .


3

У своєму проекті я представив поняття 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


1

Рішенням на 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.


1

Використовуйте це рішення Кенні застаріле, я використовую клас дій для подвійного клацання, але ви можете робити що завгодно.

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;
                    }
                });

1

Чистий 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.
    }
  }
}

0

Це працює для мене (працює на 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;
    }

0

Проблема полягає в тому, що коли ви передаєте елемент з Javascript на Java назад у Javascript, він міг покинути DOM.
Спробуйте зробити все це в Javascript:

driver.executeScript("document.querySelector('#my_id').click()") 

0

Спробуйте це

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
    }
}

0

Я знайшов тут рішення . У моєму випадку елемент стає недоступним у разі виходу з поточного вікна, вкладки чи сторінки та повернення знову.

.ignoring (StaleElement ...), .refreshed (...) та elementToBeCvable (...) не допомогли, і я отримував виняток для act.doubleClick(element).build().perform();рядка.

Використання функції в моєму основному тестовому класі:

openForm(someXpath);

Функція My 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))));
    }

0

Може існувати потенційна проблема, яка веде до 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()

0

Зазвичай StaleElementReferenceException, коли з'являється елемент, до якого ми намагаємось отримати доступ, але інші елементи можуть впливати на позицію елемента, який нас цікавить, отже, коли ми намагаємося натиснути або getText або спробувати зробити якусь дію на 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));
    }

У наведеному вище коді я спочатку намагаюся зачекати, а потім клацнути на елемент, якщо виникає виняток, тоді я ловлю його і намагаюся зациклювати, оскільки існує ймовірність того, що все-таки всі елементи можуть бути не завантажені, і знову може статися виняток.


-4

Можливо, його було додано нещодавно, але в інших відповідях не згадується неявна функція очікування Selenium, яка виконує все вищезазначене і вбудована в Selenium.

driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);

Це буде повторювати findElement()виклики, поки елемент не буде знайдений, або протягом 10 секунд.

Джерело - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp


2
Це рішення не заважає StaleElementReferenceException
MrSpock

1
Просто для усунення будь-якої плутанини у версіях, навіть в останній версії Selenium implicitlyWait () НЕ заважає StaleElementReferenceException. Я використовую метод, який викликає цикл із режимом сну до успіху або фіксованого підрахунку.
Angsuman Chakraborty
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.