การอ้างอิงองค์ประกอบเก่า: องค์ประกอบไม่ได้แนบกับเอกสารหน้า


97

ฉันมีรายการที่มีลิงก์หลายลิงก์ภายใต้แต่ละส่วน แต่ละส่วนมีลิงค์เหมือนกันฉันต้องคลิกลิงค์ใดลิงค์หนึ่งภายใต้แต่ละส่วน ฉันได้เขียนโค้ดด้านล่างนี้แล้ว แต่เมื่อรันมันทำให้ฉันเกิดstale element reference: element is not attached to the page documentข้อผิดพลาด

นี่คือรหัสของฉัน:

public static void main(String[] args) throws InterruptedException 
{
    WebDriver driver = new ChromeDriver();
    driver.navigate().to("url......");
        driver.findElement(By.id("Login1_txtEmailID")).sendKeys("hourbank5@....com");
    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++;
    }
}

}

นี่คือโครงสร้าง HTML ดังต่อไปนี้

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

Error Trace คือ:

    Exception in thread "main" 
org.openqa.selenium.StaleElementReferenceException: stale element 
reference: element is not attached to the page document

คำตอบ:


81

เส้นที่ให้ข้อยกเว้นคืออะไร ??

สาเหตุนี้เป็นเพราะองค์ประกอบที่คุณอ้างถึงถูกลบออกจากโครงสร้าง DOM

ฉันประสบปัญหาเดียวกันขณะที่ทำงานกับ IEDriver สาเหตุเกิดจากจาวาสคริปต์โหลดองค์ประกอบอีกครั้งหลังจากที่ฉันอ้างถึงดังนั้นการอ้างอิงวันที่ของฉันจึงชี้ไปที่วัตถุที่ไม่มีอยู่แม้ว่ามันจะถูกต้องบน UI ก็ตาม ฉันใช้วิธีแก้ปัญหาต่อไปนี้

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

ดูว่าสิ่งเดียวกันสามารถช่วยคุณได้หรือไม่!


linkTexts [i] = e.getText (); เส้นทำให้ฉันเกิดข้อผิดพลาดขณะวนซ้ำเป็นครั้งที่สอง
Patil Prashanth

1
แก้ไขข้อผิดพลาดเนื่องจากการรีเฟรชหน้า 1 ก่อนอื่นฉันคัดลอกข้อความลิงก์ทั้งหมดไปยังตัวแปรอาร์เรย์สตริงและใช้เพื่อวนซ้ำเพื่อคลิกลิงก์ที่ต้องการในแต่ละส่วน สำหรับ (WebElement e: LeftNavLinks) {linkTexts [i] = e.getText (); ผม ++; } สำหรับ (int j = 0; j <leftnavlen; j ++) {if (linkTexts [j] .equals (ben)) {String BenefitStatLi = "// * [@ id = 'scroll-navigation'] / li [% s ] / ก "; driver.findElement (By.xpath (String.format (BenefitStatLi, j + 1))) คลิก (); driver.findElement (By.xpath ("// * @ id = 'divContentHolder'] / div [1] / a [1]")) คลิก (); }
Patil Prashanth

ดังนั้นมันจึงเป็นเหตุผลเดียวกัน องค์ประกอบที่ระบุก่อนหน้านี้ถูกลบออกจาก DOM เนื่องจากการรีเฟรชหน้า :)
Abhishek Singh

ใช่แน่นอนขอบคุณมาก @AbhishekSingh
Rahal Kanishka

ในกรณีของฉันฉันได้รับข้อยกเว้นนี้เนื่องจากฉันไม่มีคำสั่ง break ในลูปหลังจากคลิกองค์ประกอบที่ต้องการดังนั้นระวังสถานการณ์เช่นนี้ด้วย
Raj Asapu

48

เมื่อใดก็ตามที่คุณประสบปัญหานี้เพียงกำหนดองค์ประกอบของเว็บอีกครั้งเหนือบรรทัดที่คุณได้รับข้อผิดพลาด

ตัวอย่าง:

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

เนื่องจาก DOM มีการเปลี่ยนแปลงเช่นผ่านการดำเนินการอัปเดตคุณจึงได้รับStaleElementReferenceข้อผิดพลาด

วิธีการแก้:

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


นี่ดูเหมือนจะแก้ข้อผิดพลาดของฉันได้แล้ว แต่ฉันไม่เข้าใจว่าทำไมฉันต้องกำหนดองค์ประกอบเดียวกันกับที่กำหนดไว้ก่อนหน้านี้
Abhi

8

ในการจัดการฉันใช้วิธีการคลิกต่อไปนี้ สิ่งนี้จะพยายามค้นหาและคลิกองค์ประกอบ หาก DOM เปลี่ยนไประหว่างการค้นหาและการคลิกก็จะลองอีกครั้ง แนวคิดก็คือถ้ามันล้มเหลวและฉันลองอีกครั้งทันทีความพยายามครั้งที่สองจะสำเร็จ หากการเปลี่ยนแปลง DOM รวดเร็วมากสิ่งนี้จะไม่ทำงาน

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

8

ข้อผิดพลาดนี้มีสาเหตุทั่วไป 2 ประการ: องค์ประกอบถูกลบทั้งหมดหรือองค์ประกอบไม่ได้แนบกับ DOM อีกต่อไป

หากคุณตรวจสอบแล้วว่าไม่ใช่กรณีของคุณคุณอาจประสบปัญหาเดียวกับฉัน

ไม่พบองค์ประกอบใน DOM เนื่องจากหน้าของคุณไม่ได้โหลดทั้งหมดเมื่อซีลีเนียมกำลังค้นหาองค์ประกอบ ในการแก้ปัญหานั้นคุณสามารถวางเงื่อนไขการรออย่างชัดเจนที่บอกให้ซีลีเนียมรอจนกว่าองค์ประกอบจะพร้อมให้คลิก

from selenium.webdriver.support import expected_conditions as EC

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

ดู: https://selenium-python.readthedocs.io/waits.html


สิ่งนี้ได้ผลสำหรับฉัน ลิงก์ไปยังเอกสารยังมีประโยชน์ในการทำความเข้าใจวิธีการทำงาน ขอบคุณ @Bruno_Sanches
Nikunj Kakadiya

4

สิ่งที่นี่คือคุณกำลังใช้ for loop นอกคำสั่งเงื่อนไขของคุณ

หลังจากตรงตามเงื่อนไขในคำสั่ง IF ของคุณแล้วคุณอาจไปที่หน้าอื่นดังนั้นเมื่อ for loop พยายามวนซ้ำอีกครั้งคุณจะได้รับข้อผิดพลาดขององค์ประกอบเก่าเนื่องจากคุณอยู่ในหน้าอื่น

คุณสามารถเพิ่มช่วงพักที่ส่วนท้ายของคำสั่ง if ซึ่งใช้ได้ผลสำหรับฉัน


ใช้เวลาสักพักเพื่อทำความเข้าใจว่าเหตุใดจึงเกิดข้อผิดพลาด!
BEWARB

3

เพียงแค่ทำลายลูปเมื่อคุณพบองค์ประกอบที่คุณต้องการคลิก ตัวอย่างเช่น:

  List<WebElement> buttons = getButtonElements();
    for (WebElement b : buttons) {
        if (b.getText().equals("Next"){
            b.click();
            break;
        }

2

ใช้รหัสนี้:

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

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

ลอง / จับรหัสนี้ใช้งานได้จริงสำหรับฉัน ฉันได้รับข้อผิดพลาดองค์ประกอบเก่าเหมือนกัน


1
คำตอบนี้แตกต่างจากคำตอบที่ได้รับการโหวตสูงสุดที่นี่หรือไม่?
SiKing

1

สามารถทำได้ในซีลีเนียมเวอร์ชันใหม่กว่าใน JS (แต่ทั้งหมดที่รองรับstalenessOfจะใช้งานได้):

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

0

ตามที่ @Abhishek Singh คุณต้องเข้าใจปัญหา:

เส้นที่ให้ข้อยกเว้นคืออะไร ?? สาเหตุนี้เป็นเพราะองค์ประกอบที่คุณอ้างถึงถูกลบออกจากโครงสร้าง DOM

และคุณไม่สามารถอ้างถึงได้อีกต่อไป (ลองนึกดูว่า ID ขององค์ประกอบใดเปลี่ยนแปลงไป)

ทำตามรหัส:

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

ตอนนี้ให้เราสงสัยว่าทำไม?

  1. btnTurnOff พบโดยคนขับ - ตกลง
  2. btnTurnOff ถูกแทนที่ด้วย btnTurnOn - ตกลง
  3. btnTurnOnถูกพบโดยคนขับ - ตกลง
  4. btnTurnOnถูกแทนที่ด้วยbtnTurnOff - ตกลง
  5. เราเรียกthis.btnTurnOff.isDisplayed();ในองค์ประกอบที่ไม่อยู่อีกต่อไปในความรู้สึกซีลีเนียม - คุณสามารถเห็นมันทำงานอย่างสมบูรณ์แบบ แต่มันเป็นตัวอย่างที่แตกต่างกันของปุ่มเดียวกัน

การแก้ไขที่เป็นไปได้:

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

0

ในกรณีของฉันฉันมีหน้าที่เป็นinput type='date'ข้อมูลอ้างอิงที่ฉันได้รับในการโหลดหน้าเว็บ แต่เมื่อฉันพยายามโต้ตอบมันแสดงให้เห็นสิ่งนี้exceptionและมีความหมายมากเนื่องจากJavascriptมีการควบคุมการควบคุมของฉันดังนั้นจึงแยกออกจากเอกสาร และฉันต้องre-getอ้างอิงหลังจากที่จาวาสคริปต์ทำงานด้วยการควบคุม ดังนั้นนี่คือลักษณะที่รหัสของฉันปรากฏก่อนข้อยกเว้น:

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

รหัสหลังจากยกข้อยกเว้น:

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.

ดังนั้นตอนนี้คุณสามารถดำเนินการเพิ่มเติมกับรหัสของคุณหรือคุณสามารถออกจากไดรเวอร์ได้หากไม่ประสบความสำเร็จในการควบคุมให้ทำงาน

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

สิ่งที่ฉันได้เรียนรู้มาจนถึงตอนนี้ก็คือการจับข้อยกเว้นที่ควรรู้เกี่ยวกับการเรียกใช้โค้ดที่ประสบความสำเร็จไม่ใช่ความคิดที่ดี แต่ฉันต้องทำและพบว่าสิ่งนี้work-aroundใช้ได้ดีในกรณีนี้

PS: javaหลังจากที่เขียนทั้งหมดนี้ผมเพิ่งสังเกตเห็นแท็กที่หัวข้อนี้เป็น ตัวอย่างโค้ดนี้ใช้เพื่อการสาธิตเท่านั้นซึ่งอาจช่วยผู้ที่มีปัญหาด้านC#ภาษา หรือแปลได้ง่ายๆjavaว่าไม่มีC#รหัสเฉพาะมากนัก


-7

ใช้รหัสนี้เพื่อรอจนกว่าจะแนบองค์ประกอบ:

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

    }

1
ไม่ได้ผลสำหรับฉัน แต่ยังคงเป็นแนวทางที่น่าสนใจ
Yar

28
สิ่งนี้ไม่สมเหตุสมผลเลย
แซม

1
มันสมเหตุสมผลแล้วมันไม่ชัดเจนที่สุดในแง่ของชื่อตัวแปร "breakIt" สิ่งที่ทำคือแยกออกจากลูป while (จริง) ทันทีที่ข้อผิดพลาด "ไม่ได้แนบองค์ประกอบ" จะไม่ถูกโยนอีกต่อไป
Emery

'บูลีน doIt = จริง; ในขณะที่ (doIt) {ลอง {// เขียนโค้ดของคุณที่นี่} catch (Exception e) {if (e.getMessage (). contains ("element is not attached")) {doIt = false; }}} '
Tao Zhang

1
มีกลิ่นเหมือนวิญญาณโกโต
Eugene Baranovsky
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.