nieaktualne odniesienie do elementu: element nie jest dołączony do dokumentu strony

102

Mam listę, która zawiera wiele linków w każdej sekcji. Każda sekcja ma te same łącza, których potrzebuję, aby kliknąć określone łącze w każdej sekcji. Napisałem poniższy kod, ale po wykonaniu stale element reference: element is not attached to the page documentwyświetla mi się błąd.

To jest mój kod:

public static void main(String[] args) throws InterruptedException 
{
    WebDriver driver = new ChromeDriver();
    driver.navigate().to("url......");
        driver.findElement(By.id("Login1_txtEmailID")).sendKeys("[email protected]");
    driver.findElement(By.id("Login1_txtPassword")).sendKeys("Testing1*");
    driver.findElement(By.id("Login1_btnLogin")).click();
    List<WebElement> LeftNavLinks=driver.findElements(By.xpath("//*[@id='sliding-navigation']//a"));
    Thread.sleep(1000);
    String ben="Benefit Status";
    String[] linkTexts = new String[LeftNavLinks.size()];
    int i = 0;
    for (WebElement e : LeftNavLinks) 
    {   
        linkTexts[i] = e.getText();
        System.out.print(i+" " + linkTexts[i]+"\n");
        if(linkTexts[i].equals(ben))
        {
            String BenefitStatLi="//*[@id='sliding-navigation']/li[%s]/a";
            System.out.print(i+" " + linkTexts[i]+"\n");
                driver.findElement(By.xpath(String.format(BenefitStatLi,i))).click();
            driver.findElement(By.xpath("//* [@id='divContentHolder']/div[1]/a[1]")).click();
        }
        i++;
    }
}

}

To jest struktura HTML, jak poniżej

<div id="ucAdminMenu_divMenu">
  <ul id="sliding-navigation">
    <li class="sliding-element">
      <a href=" ">Claims Status</a>
    </li>
    <li class="sliding-element">
      <a href=" ">Eligibility Status</a>
    </li>
    <li class="sliding-element">
      <h3>Section-1</h3>
    </li>
    <li class="sliding-element">
      <a href=" ">Forms and Documents</a>
    </li>
    <li class="sliding-element">
      <a href=" HourBank.aspx?id=002">Hour Bank</a>
    </li>
    <li class="sliding-element">
      <h3>Section-2</h3>
    </li>
    <li class="sliding-element">
      <a href=" ">Benefit Status</a>
    </li>
    <li class="sliding-element">
      <a href=" ">Forms and Documents</a>
    </li>
    <li class="sliding-element">
      <h3>Section-3</h3>
    </li>
    <li class="sliding-element">
      <a href=" ">Forms and Documents</a>
    </li>
    <li class="sliding-element">
      <h3>Testing Fund</h3>
    </li>
    <li class="sliding-element">
      <a href=" ">Benefit Status</a>
    </li>
    <li class="sliding-element">
      <a href=" ">Order ID Card</a>
    </li>
  </ul>
</div>

Ślad błędu to:

    Exception in thread "main" 
org.openqa.selenium.StaleElementReferenceException: stale element 
reference: element is not attached to the page document
Patil Prashanth
źródło

Odpowiedzi:

84

Jaka jest linia, która stanowi wyjątek?

Powodem tego jest fakt, że element, do którego się odnosisz, został usunięty ze struktury DOM

Miałem ten sam problem podczas pracy z IEDriver. Powodem było to, że javascript załadował element jeszcze raz po tym, jak odniosłem się, więc moje odniesienie do daty wskazywało na nieistniejący obiekt, nawet jeśli miał rację w interfejsie użytkownika. Użyłem następującego obejścia.

try {
    WebElement date = driver.findElement(By.linkText(Utility.getSheetData(path, 7, 1, 2)));
    date.click();
}
catch(org.openqa.selenium.StaleElementReferenceException ex)
{
    WebElement date = driver.findElement(By.linkText(Utility.getSheetData(path, 7, 1, 2)));
    date.click();
}

Sprawdź, czy to samo może Ci pomóc!

Abhishek Singh
źródło
linkTexts [i] = e.getText (); linie daje mi błąd podczas pętli po raz drugi
Patil Prashanth
1
Rozwiązano problem związany z odświeżaniem strony 1. Najpierw skopiowałem wszystkie teksty linków do zmiennej tablicowej typu String, a później użyłem pętli for, aby kliknąć wymagane łącza w każdej sekcji. dla (WebElement e: LeftNavLinks) {linkTexts [i] = e.getText (); i ++; } for (int j = 0; j <leftnavlen; j ++) {if (linkTexts [j] .equals (ben)) {String BenefitStatLi = "// * [@ id = 'slide-navigation'] / li [% s ]/za"; driver.findElement (By.xpath (String.format (BenefitStatLi, j + 1))). click (); driver.findElement (By.xpath ("// * @ id = 'divContentHolder'] / div [1] / a [1]")). click (); }
Patil Prashanth
Więc to było z tego samego powodu. Zidentyfikowane wcześniej elementy zostały usunięte z DOM z powodu odświeżenia strony :)
Abhishek Singh
Tak, rzeczywiście, dziękuję @AbhishekSingh
Rahal Kanishka
W moim przypadku dostałem ten wyjątek, ponieważ nie mam instrukcji break w pętli po kliknięciu wymaganego elementu, więc uważaj również na takie sytuacje.
Raj Asapu
49

Ilekroć napotkasz ten problem, po prostu zdefiniuj ponownie element sieciowy powyżej linii, w której pojawia się błąd.

Przykład:

WebElement button = driver.findElement(By.xpath("xpath"));
button.click();

//here you do something like update or save 

//then you try to use the button WebElement again to click 
button.click();

Ponieważ DOM zmienił się, np. W wyniku akcji aktualizacji, otrzymujesz StaleElementReferencebłąd.

Rozwiązanie:

WebElement button = driver.findElement(By.xpath("xpath"));
button.click();

//here you do something like update or save 

//then you define the button element again before you use it
WebElement button1 = driver.findElement(By.xpath("xpath"));
//that new element will point to the same element in the new DOM
button1.click();

rohit goudar
źródło
Hej, to chyba rozwiązało mój błąd. Ale nie rozumiem, dlaczego muszę definiować ten sam element, definiując go wcześniej.
Abhi
Wielka pomoc. Ale czy możesz wyjaśnić, dlaczego DOM zmienił się dla tej samej zmiennej? Dzięki.
JuBaer AD
@JuBaerAD - Potwierdziłby, że może całkowicie na frameworku (reagować / vue / others). Prosty „bezpośredni” kod HTML byłby trudny do przewidzenia. Ale używając frameworka, może zrobić coś zupełnie dla siebie, a efekt uboczny jest taki. Źle? Przede wszystkim czy interfejs użytkownika nadal działa, tj. Interfejs użytkownika React nadal działa i działa w razie potrzeby? Tak, nie ma problemu ... Testuję interfejs użytkownika od ponad 20 lat, widziałem interfejsy użytkownika, w których były tylko rzeczywiste kontrolki po odwołaniu się do kontrolki (fajnie jest nie testować) i za każdym razem, gdy uzyskiwałeś dostęp z technicznego punktu widzenia, otrzymywałem nową kontrolę .. Podejrzewam, że to samo ...
Dano
9

Te błędy mają dwie typowe przyczyny: element został całkowicie usunięty lub element nie jest już powiązany z DOM.

Jeśli już sprawdziłeś, czy to nie jest Twój przypadek, możesz mieć ten sam problem co ja.

Element w DOM nie został znaleziony, ponieważ Twoja strona nie jest całkowicie załadowana, gdy Selenium szuka elementu. Aby rozwiązać ten problem, możesz umieścić jawny warunek oczekiwania, który mówi Selenium, aby czekał, aż element będzie dostępny do kliknięcia.

from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.ID, 'someid')))

Zobacz: https://selenium-python.readthedocs.io/waits.html

Bruno Sanches
źródło
to działało dla mnie. Również link do dokumentu był pomocny w zrozumieniu, jak to działa. Dzięki @Bruno_Sanches
Nikunj Kakadiya
8

Aby sobie z tym poradzić, używam następującej metody kliknięcia. Spowoduje to próbę znalezienia i kliknięcia elementu. Jeśli DOM zmieni się między znalezieniem a kliknięciem, spróbuje ponownie. Chodzi o to, że jeśli się nie powiedzie i spróbuję natychmiast, druga próba się powiedzie. Jeśli zmiany DOM są bardzo szybkie, to nie zadziała.

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;
}
Alisha Raju
źródło
4

Chodzi o to, że używasz pętli for poza instrukcją warunkową.

Po spełnieniu warunków w instrukcji IF prawdopodobnie przechodzisz do innej strony, więc gdy pętla for ponownie spróbuje wykonać iterację, zostanie wyświetlony błąd przestarzałego elementu, ponieważ jesteś na innej stronie.

Możesz dodać przerwę na końcu swojego oświadczenia if, to zadziałało dla mnie.

Adrian Coroi
źródło
Zajęło mi trochę czasu, zanim zrozumiałem, dlaczego pojawił się błąd!
BEWARB
3

Po prostu przerwij pętlę, gdy znajdziesz element, który chcesz kliknąć. na przykład:

  List<WebElement> buttons = getButtonElements();
    for (WebElement b : buttons) {
        if (b.getText().equals("Next"){
            b.click();
            break;
        }
אדם נחמיאס
źródło
2

Użyj tego kodu:

public class LinkTest 
{   
    public static void main(String[] args) 
    {
        WebDriver driver = new FirefoxDriver();
        driver.navigate().to("file:///C:/Users/vkiran/Desktop/xyz.html");
        List<WebElement> alllinks =driver.findElements(By.xpath("//*[@id='sliding-navigation']//a"));
        String a[]=new String[alllinks.size()];
        for(int i=0;i<alllinks.size();i++)
        {
            a[i]=alllinks.get(i).getText(); 
            if(a[i].startsWith("B"))
            {
                System.out.println("clicking on this link::"+driver.findElement(By.linkText(a[i])).getText());
                driver.findElement(By.linkText(a[i])).click();  

            }
            else
            {
                System.out.println("does not starts with B so not clicking");
            }
        }
}
}
kiran
źródło
1
try {
    WebElement button = driver.findElement(By.xpath("xpath"));
            button.click();
}
catch(org.openqa.selenium.StaleElementReferenceException ex)
{
    WebElement button = driver.findElement(By.xpath("xpath"));
            button.click();
}

Ten kod try / catch faktycznie zadziałał dla mnie. Otrzymałem ten sam błąd nieaktualnego elementu.

Charan Raz
źródło
1
Czy ta odpowiedź różni się w jakiś sposób od odpowiedzi najczęściej wybieranej tutaj?
SiKing
1

Można to zrobić w nowszych wersjach selenu w JS (ale wszystkie wspierające nieaktualność będą działać):

 const { until } = require('selenium-webdriver');
 driver.wait(
        until.stalenessOf(
          driver.findElement(
            By.css(SQLQueriesByPhpMyAdminSelectors.sqlQueryArea)
          )
        ),
        5 * 1000
      )
      .then( driver.findElement(By.css(SQLQueriesByPhpMyAdminSelectors.sqlQueryArea))
      .sendKeys(sqlString)
  );
SkorpEN
źródło
0

Zgodnie z @Abhishek Singh's musisz zrozumieć problem:

Jaka jest linia, która stanowi wyjątek? Powodem tego jest fakt, że element, do którego się odnosisz, został usunięty ze struktury DOM

i nie możesz już się do niego odwoływać (wyobraź sobie, jaki identyfikator elementu się zmienił).

Postępuj zgodnie z kodem:

class TogglingPage {
  @FindBy(...)
  private WebElement btnTurnOff;

  @FindBy(...)
  private WebElement btnTurnOn;

  TogglingPage turnOff() {
    this.btnTurnOff.isDisplayed();  
    this.btnTurnOff.click();          // when clicked, button should swap into btnTurnOn
    this.btnTurnOn.isDisplayed();
    this.btnTurnOn.click();           // when clicked, button should swap into btnTurnOff
    this.btnTurnOff.isDisplayed();    // throws an exception
    return new TogglingPage();
  }
}

Zastanówmy się teraz, dlaczego?

  1. btnTurnOff został znaleziony przez kierowcę - ok
  2. btnTurnOff został zastąpiony przez btnTurnOn - ok
  3. btnTurnOnzostał znaleziony przez kierowcę. - dobrze
  4. btnTurnOn został zastąpiony przez btnTurnOff - ok
  5. odwołujemy this.btnTurnOff.isDisplayed();się do elementu, który już nie istnieje w sensie Selenium - widać, działa doskonale, ale jest to inna instancja tego samego przycisku .

Możliwa poprawka:

  TogglingPage turnOff() {
    this.btnTurnOff.isDisplayed();  
    this.btnTurnOff.click();

    TogglingPage newPage = new TogglingPage();
    newPage.btnTurnOn.isDisplayed();
    newPage.btnTurnOn.click();

    TogglingPage newerPage = new TogglingPage();
    newerPage.btnTurnOff.isDisplayed();    // ok
    return newerPage;
  }
J.Wincewicz
źródło
0

W moim przypadku miałem stronę, na której była to input type='date'referencja, którą otrzymałem podczas ładowania strony, ale kiedy próbowałem z nią wejść w interakcję, pokazało to exceptioni to było dość znaczące, ponieważ Javascriptmanipulowałem moją kontrolą, dlatego zostało odłączone od dokumentu i musiałemre-get odwołanie po tym, jak javascript wykonał swoją pracę z formantem. A więc tak wyglądał mój kod przed wyjątkiem:

if (elemDate != null)
{ 
    elemDate.Clear(); 
    elemDate.SendKeys(model.Age);
}

Kod po zgłoszeniu wyjątku:

int tries = 0;
do
{
    try
    {
        tries++;
        if (elemDate != null)
        {
            // these lines were causing the exception so I had break after these are successfully executed because if they are executed that means the control was found and attached to the document and we have taken the reference of it again.
            elemDate.Clear();
            elemDate.SendKeys(model.Age);
            break;
        }
    }
    catch (StaleElementReferenceException)
    {
        System.Threading.Thread.Sleep(10); // put minor fake delay so Javascript on page does its actions with controls
        elemDate = driver.FindElement(By.Id(dateId));
    }
} while (tries < 3); // Try it three times.

Więc teraz możesz wykonać dalsze działania z kodem lub możesz zamknąć sterownik, jeśli nie udało mu się uruchomić kontroli.

if(tries > 2)
{
   // element was not found, find out what is causing the control detachment.
   // driver.Quit();
   return;
}

// Hurray!! Control was attached and actions were performed.
// Do something with it...

Coś, czego się do tej pory nauczyłem, to wychwytywanie wyjątków, aby wiedzieć o pomyślnym wykonaniu kodu, nie jest dobrym pomysłem, ale musiałem to zrobić i znalazłem to work-around się, że działa to dobrze w tym przypadku.

PS: Po napisaniu tego wszystkiego, właśnie zauważyłem tagi, dla których był ten wątek java. Ten przykładowy kod służy tylko do celów demonstracyjnych. Może pomóc osobom, które mają problem z C#językiem. Lub można go łatwo przetłumaczyć, javaponieważ nie ma zbyt C#konkretnego kodu.

Jamshaid K.
źródło
-7

użyj tego kodu, aby poczekać, aż element zostanie dołączony:

boolean breakIt = true;
        while (true) {
        breakIt = true;
        try {
            // write your code here
        } catch (Exception e) {
            if (e.getMessage().contains("element is not attached")) {
                breakIt = false;
            }
        }
        if (breakIt) {
            break;
        }

    }

źródło
1
nie zadziałało dla mnie, ale wciąż interesujące podejście
Yar
28
To nie ma sensu.
Sam
1
Ma to sens, po prostu nie jest najbardziej jasne, jeśli chodzi o nazwę zmiennej „breakIt”. To powoduje wyrwanie się z pętli while (prawda), gdy tylko błąd „element nie jest dołączony” nie jest już generowany.
emery
'boolean doIt = true; while (doIt) {try {// napisz swój kod tutaj} catch (Exception e) {if (e.getMessage (). zawiera ("element nie jest dołączony")) {doIt = false; }}} '
Tao Zhang
1
pachnie jak duch goto
Eugene Baranovsky