จะหลีกเลี่ยง“ StaleElementReferenceException” ในซีลีเนียมได้อย่างไร?


90

ฉันกำลังใช้การทดสอบซีลีเนียมจำนวนมากโดยใช้ Java บางครั้งการทดสอบของฉันล้มเหลวเนื่องจากไฟล์StaleElementReferenceException. คุณช่วยแนะนำแนวทางในการทำให้การทดสอบมีเสถียรภาพมากขึ้นได้ไหม

คำตอบ:


92

สิ่งนี้สามารถเกิดขึ้นได้หากการดำเนินการ 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
สามารถแก้ไขได้โดยใช้การอ้างอิงองค์ประกอบที่แตกต่างกัน
Ripon Al Wasim

@jspcal สิ่งนี้เป็นเสน่ห์สำหรับฉัน! ขอบคุณมาก!
Anthony

หากข้างต้นไม่สามารถแก้ปัญหาได้การอัปเดตเป็น chromedriver ล่าสุดคือสิ่งที่แก้ไขได้สำหรับเรา
Vdex

5
นี่เป็นสิ่งที่น่ากลัวในหลาย ๆ ด้าน มันช่วยแก้ปัญหาปัจจุบันของฉันได้ดีขอบคุณมาก
Software Engineer

68

ฉันมีปัญหานี้เป็นระยะ ๆ ไม่เป็นที่รู้จักสำหรับฉัน BackboneJS กำลังทำงานอยู่บนเพจและแทนที่องค์ประกอบที่ฉันพยายามคลิก รหัสของฉันมีลักษณะเช่นนี้

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

ซึ่งแน่นอนว่าการทำงานจะเหมือนกับสิ่งนี้

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

สิ่งที่จะเกิดขึ้นเป็นครั้งคราวคือจาวาสคริปต์จะแทนที่องค์ประกอบ checkoutLink ระหว่างการค้นหาและคลิกกล่าวคือ

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

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

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

20

โดยทั่วไปเกิดจากการอัปเดต DOM และคุณพยายามเข้าถึงองค์ประกอบที่อัปเดต / ใหม่ - แต่ DOM ได้รับการรีเฟรชจึงเป็นการอ้างอิงที่ไม่ถูกต้องที่คุณมี ..

หลีกเลี่ยงปัญหานี้โดยใช้การรออย่างชัดเจนในองค์ประกอบก่อนเพื่อให้แน่ใจว่าการอัปเดตเสร็จสมบูรณ์จากนั้นรับข้อมูลอ้างอิงใหม่ไปยังองค์ประกอบอีกครั้ง

นี่คือบางส่วนรหัสหลอกเพื่อแสดงให้เห็น (ดัดแปลงมาจากบางรหัส 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.

หวังว่านี่จะช่วยได้!


17

วิธีแก้ปัญหาของ Kenny นั้นดี แต่ก็สามารถเขียนได้อย่างสวยงามมากขึ้น

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 ซึ่งจัดการเรื่องประเภทนี้และอื่น ๆ (แทนที่จะใช้การอ้างอิงองค์ประกอบจะจัดการกับพร็อกซีดังนั้นคุณจึงไม่ต้องจัดการกับองค์ประกอบเก่าซึ่งอาจเป็นเรื่องยาก) เซเลไนด์


ข้อจำกัดความรับผิดชอบ: ฉันเป็นแค่ผู้ใช้ selenide ที่มีความสุขไม่มีอะไรเกี่ยวข้องกับการพัฒนา
cocorossello

วิธีที่สองของคุณจะใช้งานได้เนื่องจากองค์ประกอบหยุดทำงานเมื่อคุณไม่คลิกเมื่อคุณพบ
Rajagopalan

ใช้ selenide เพื่อหลีกเลี่ยงปัญหานี้ง่ายกว่ามาก ซีลีเนียมไม่ได้ถูกนำมาใช้เพียงอย่างเดียวเนื่องจากปัญหานี้และความจริงที่ว่าเป็น API ระดับต่ำสำหรับผู้ใช้ทั่วไป
cocorossello

ฉันตระหนักดีถึงเรื่องนี้ ฉันใช้ WATIR ซึ่งเป็นกระดาษห่อหุ้มรอบ Ruby Selenium Binding WATIR จะดูแลปัญหาเหล่านี้ทั้งหมดโดยอัตโนมัติ (สำหรับองค์ประกอบที่ค้างในอินสแตนซ์) ฉันกำลังมองหาสิ่งที่เทียบเท่าในการผูก Java ฉันพบ Selenide แต่ฉันไม่รู้วิธีเปลี่ยนการรอโดยปริยายและการรออย่างชัดเจนใน selenide คุณสามารถบอกฉันได้ไหมว่าต้องทำอย่างไร? หรือมีเนื้อหาใดที่คุณสามารถเสนอให้ฉันได้ที่ฉันสามารถอ้างอิงได้หรือไม่? และคุณมีความคิดเห็นอย่างไรกับ FluentLenium?
Rajagopalan

1
ทุกคนควรรู้ว่าคำตอบที่เลือกโดย OP ย้อนกลับไปในปี 2012 หลายสิ่งหลายอย่างเปลี่ยนแปลงไปใน 7 ปีที่ผ่านมา คำตอบนี้ถูกต้องกว่าสำหรับปี 2019
hfontanez

11

สาเหตุที่ทำให้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(). สิ่งนี้จะรอและตรวจสอบว่าองค์ประกอบที่เป็นปัญหาได้รับการรีเฟรชในช่วงระยะหมดเวลาที่กำหนดหรือไม่และรอให้องค์ประกอบนั้นคลิกได้

มีลักษณะที่เป็นเอกสารสำหรับวิธีการฟื้นฟู


4

ในโครงการของฉันฉันได้แนะนำแนวคิดเกี่ยวกับ StableWebElement เป็น Wrapper สำหรับ WebElement ซึ่งสามารถตรวจจับได้ว่าองค์ประกอบเป็น Stale หรือไม่และค้นหาการอ้างอิงใหม่ไปยังองค์ประกอบเดิม ฉันได้เพิ่มวิธีการช่วยเหลือในการค้นหาองค์ประกอบที่ส่งคืน 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

โซลูชันของ Kenny เลิกใช้แล้วให้ใช้สิ่งนี้ฉันใช้คลาสการดำเนินการเพื่อดับเบิลคลิก แต่คุณสามารถทำอะไรก็ได้

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

ทำความสะอาดวิธีการที่จับได้อย่างสง่างามfindByAndroidIdStaleElementReference

นี่ขึ้นอยู่กับคำตอบของ jspcalเป็นอย่างมากแต่ฉันต้องแก้ไขคำตอบนั้นเพื่อให้มันทำงานได้อย่างสมบูรณ์กับการตั้งค่าของเราดังนั้นฉันจึงต้องการเพิ่มที่นี่ในกรณีที่เป็นประโยชน์กับผู้อื่น ถ้าคำตอบนี้ช่วยให้คุณโปรดไป upvote คำตอบ 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 (... ) และ elementToBeClicable (... ) ไม่ได้ช่วยและฉันได้รับข้อยกเว้นในact.doubleClick(element).build().perform();สตริง

การใช้ฟังก์ชันในคลาสทดสอบหลักของฉัน:

openForm(someXpath);

ฟังก์ชัน 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

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

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.