2012-10-19 18 views

Odpowiedz

44

Może się tak zdarzyć, jeśli operacja DOM na stronie tymczasowo powoduje, że element nie jest dostępny. Aby umożliwić te przypadki, możesz spróbować uzyskać dostęp do elementu kilka razy w pętli, zanim w końcu rzucisz wyjątek.

Spróbuj this excellent solution from 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; 
} 
+0

Wow! Tego właśnie potrzebowałem. Dzięki! – SpartaSixZero

+0

Można to również naprawić, używając innego odniesienia elementu. –

+0

To, że przy 2 próbach jest bardzo brudnym rozwiązaniem, powinno być preferowane oczekiwanie. Zobacz moje rozwiązanie – cocorossello

11

Zazwyczaj jest to spowodowane DOM aktualizowany i próbuje uzyskać dostęp do zaktualizowanej/nowy element - ale Doma odświeżony więc nieprawidłowy odniesienie masz ..

Najpierw obejmij to, używając wyraźnego oczekiwania na element, aby upewnić się, że aktualizacja została zakończona, a następnie ponownie pobierz nowe odniesienie do elementu.

Oto niektóre kodu do zilustrowania psuedo (Zaczerpnięte z niektórych kodu C# używam do dokładnie tym numerze):

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. 

Nadzieja to pomaga!

40

Miałem ten problem sporadycznie. Bez wiedzy, BackboneJS działał na stronie i zastępował element, który próbowałem kliknąć. Mój kod wyglądał tak.

driver.findElement(By.id("checkoutLink")).click(); 

Który jest oczywiście funkcjonalnie taki sam jak ten.

WebElement checkoutLink = driver.findElement(By.id("checkoutLink")); 
checkoutLink.click(); 

Co by się stało, to javascript zastąpiłby element checkoutLink pomiędzy znajdowaniem i klikaniem, czyli.

WebElement checkoutLink = driver.findElement(By.id("checkoutLink")); 
// javascript replaces checkoutLink 
checkoutLink.click(); 

Które słusznie doprowadziła do StaleElementReferenceException podczas próby kliknij link. Nie mogłem znaleźć żadnego wiarygodnego sposobu, aby poinformować WebDrivera, że ​​powinien poczekać, aż javascript się skończy, więc oto jak ostatecznie go rozwiązałem.

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

Kod ten będzie stale próbować kliknij link, ignorując StaleElementReferenceExceptions dopóki nie uda kliknięcia lub limit czasu zostanie osiągnięty. Podoba mi się to rozwiązanie, ponieważ pozwala zaoszczędzić na pisaniu jakiejkolwiek logiki ponownej próby i wykorzystuje tylko wbudowane konstrukcje WebDrivera.

-4

Być może dodano go niedawno, ale inne odpowiedzi nie wspominają o domyślnej funkcji czekania Selenium, która spełnia wszystkie powyższe warunki i jest wbudowana w Selen.

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

To będzie ponowić findElement() połączeń aż element został znaleziony, lub przez 10 sekund.

Source - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp

+1

To rozwiązanie nie zapobiega StaleElementReferenceException – MrSpock

+0

Tylko w celu wyeliminowania wszelkich nieporozumień w wersjach, nawet w najnowszej wersji programu implicitlyWait() NIE zapobiega wystąpieniu wyjątku StaleElementReferenceException. Używam metody, która wywołuje pętlę ze snu, aż do sukcesu lub ustalonej liczby. –

1

Rozwiązanie w C# byłoby:

klasa Pomocnik:

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

Inwokacja:

 DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10); 
     driverHelper.ClickElement("input[value='csharp']:first-child"); 

Podobnie może być stosowany do Javy.

0

Powód, dla którego występuje StaleElementReferenceException, został już opracowany: aktualizacje DOM między znalezieniem a zrobieniem czegoś z elementem.

Dla click-Problem I niedawno używany rozwiązanie takiego:

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

Zasadniczą częścią jest „łańcuchowym” selenu własnego ExpectedConditions poprzez ExpectedConditions.refreshed(). To faktycznie czeka i sprawdza, czy element został odświeżony podczas określonego czasu oczekiwania i dodatkowo czeka, aż element stanie się klikalny.

Spójrz na documentation for the refreshed method.

4

Kenny rozwiązanie jest dobre, jednak to może być napisany w bardziej elegancki sposób

new WebDriverWait(driver, timeout) 
     .ignoring(StaleElementReferenceException.class) 
     .until((WebDriver d) -> { 
      d.findElement(By.id("checkoutLink")).click(); 
      return true; 
     }); 

Albo też:

new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink"))); 
driver.findElement(By.id("checkoutLink")).click(); 
0

W moim projekcie wprowadził pojęcie StableWebElement. Jest to wrapper dla WebElement, który jest w stanie wykryć, czy element jest Stale i znaleźć nowe odniesienie do oryginalnego elementu. Dodałem metody pomocnika do lokalizowania elementów, które zwracają StableWebElement zamiast WebElement i problem z StaleElementReference zniknął.

public static IStableWebElement FindStableElement(this ISearchContext context, By by) 
{ 
    var element = context.FindElement(by); 
    return new StableWebElement(context, element, by, SearchApproachType.First); 
} 

Kod w C# jest dostępny na stronie mojego projektu, ale to może być łatwo przeniesione do java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs

0

Działa to dla mnie (100% sprawny) przy użyciu 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; 
    }