Selenium:在遍历一组元素时如何避免StaleElementReferenceException?

时间:2019-08-15 15:41:13

标签: java selenium selenium-webdriver

我有一个包含一堆桌子的页面。我在外部循环中遍历表,然后在内部循环中遍历表中的每一行。一切正常。但是某些页面具有“下一步”按钮。当我在完成页面后添加代码以单击它时,然后在遍历表的各行时开始获取StaleElementReferenceException。

代码如下:

WebDriverWait wait1 = new WebDriverWait(driver, 10000);
WebElement maxPage = null;
WebElement auctionsWaitingDiv = driver.findElement(By.cssSelector("div[class='Head_W']"));
if (auctionsWaitingDiv.isDisplayed() == false) return properties;

try {
    maxPage = wait1.until(ExpectedConditions.visibilityOfElementLocated(By.id("maxWA")));
} catch (TimeoutException ex) {
    return properties;
}

Integer maxPageNo = 1;
if (!maxPage.getText().isEmpty()) 
    maxPageNo = Integer.parseInt(maxPage.getText());
for (int i = 1; i <= maxPageNo; i++) {
    driver.findElement(By.cssSelector("div[id='Area_W']"));    //only look at Auctions Waiting section
    WebDriverWait wait2 = new WebDriverWait(driver, 10000);
    List<WebElement> tables = null;
    try {
        tables = wait2.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.cssSelector("table[class='ad_tab']")));
    } catch (TimeoutException ex) {
        System.out.println("table not found in allotted time");
        return properties;
    } catch (StaleElementReferenceException ex) {
        System.out.println("returning due to StaleElementReferenceException");
        return properties;

    }
    for (WebElement table: tables) {  
        List<String> propAttributes = new ArrayList<>();

        // StaleElementReferenceException: The element reference of
        // <table class="ad_tab"> is stale; either the element is no
        // longer attached to the DOM, it is not in the current
        // frame context, or the document has been refreshed
        List<WebElement> rows = table.findElements(By.cssSelector("tr"));

        String parcelLink = "";
        for (WebElement row : rows) { 
            WebElement key = row.findElement(By.cssSelector("th"));
            WebElement val = row.findElement(By.cssSelector("td"));
            String keyVal = key.getText() + val.getText();
            propAttributes.add(keyVal);
            if (key.getText().equals("Parcel ID:")) {
                WebElement a = val.findElement(By.cssSelector("a"));
                parcelLink = a.getAttribute("href");
            }
        }
    }
    driver.findElement(By.xpath(".//*[@class='PageRight']")).click();  //click the "Next" button
}

我不明白的是,为什么过时的因素根本没有发生?该页面在循环期间没有改变,我一直等到所有元素都被取走。如何避免StaleElementReferenceException?

编辑:最后的堆栈跟踪显示此行正在发生:

List<WebElement> rows = table.findElements(By.cssSelector("tr"));

及其上面的错误消息显示:

  

严重:空

     

org.openqa.selenium.StaleElementReferenceException:<table class="ad_tab">的元素引用是陈旧的;要么元素不再附加到DOM,它不在当前框架上下文中,或者文档已刷新

2 个答案:

答案 0 :(得分:1)

每当您要访问不再可用的元素引用时,都会引发StaleElementReferenceException。当元素不再附加到DOM或页面已更新时,就会发生这种情况。

解决方案是在发生这种情况时再次搜索元素。 您可以调整所有测试或页面对象。或者,您编写自己的RobustWebDriver和RobustWebElement,如果引发SERE则刷新该元素。

RobustWebDriver:

public class RobustWebDriver implements WebDriver {

    private WebDriver originalWebDriver;

    public RobustWebDriver(WebDriver webDriver) {
        this.originalWebDriver = webDriver;
    }

    @Override
    public void get(String url) {
        this.originalWebDriver.get(url);
    }

    @Override
    public String getCurrentUrl() {
        return this.originalWebDriver.getCurrentUrl();
    }

    @Override
    public String getTitle() {
        return this.originalWebDriver.getTitle();
    }

    @Override
    public List<WebElement> findElements(By by) {
        List<WebElement> elements = new ArrayList<>();
        for (WebElement element : this.originalWebDriver.findElements(by)) {
            elements.add(new RobustWebElement(element, by, this));
        }
        return elements;
    }

    @Override
    public WebElement findElement(By by) {
        return new RobustWebElement(this.originalWebDriver.findElement(by), by, this);
    }

    @Override
    public String getPageSource() {
        return this.originalWebDriver.getPageSource();
    }

    @Override
    public void close() {
        this.originalWebDriver.close();

    }

    @Override
    public void quit() {
        this.originalWebDriver.quit();
    }

    @Override
    public Set<String> getWindowHandles() {
        return this.originalWebDriver.getWindowHandles();
    }

    @Override
    public String getWindowHandle() {
        return this.originalWebDriver.getWindowHandle();
    }

    @Override
    public TargetLocator switchTo() {
        return this.originalWebDriver.switchTo();
    }

    @Override
    public Navigation navigate() {
        return this.originalWebDriver.navigate();
    }

    @Override
    public Options manage() {
        return this.originalWebDriver.manage();
    }
}

RobustWebElement:

public class RobustWebElement implements WebElement {

    private WebElement originalElement;
    private RobustWebDriver driver;
    private By by;
    private static final int MAX_RETRIES = 10;

    public RobustWebElement(WebElement element, By by, RobustWebDriver driver) {
        this.originalElement = element;
        this.by = by;
        this.driver = driver;
    }

    @Override
    public void click() {
        int retries = 0;
        while (retries < MAX_RETRIES) {
            try {
                this.originalElement.click();
                return;
            } catch (StaleElementReferenceException ex) {
                refreshElement();
            }
            retries++;
        }
        throw new StaleElementReferenceException(
                String.format("Element is still stale after %s retries.", MAX_RETRIES));
    }

    @Override
    public void sendKeys(CharSequence... keysToSend) {
        int retries = 0;
        while (retries < MAX_RETRIES) {
            try {
                this.originalElement.sendKeys(keysToSend);
                return;
            } catch (StaleElementReferenceException ex) {
                refreshElement();
            }
            retries++;
        }
        throw new StaleElementReferenceException(
                String.format("Element is still stale after %s retries.", MAX_RETRIES));
    }

    // TODO add other unimplemented methods with similar logic.

    private void refreshElement() {
        this.originalElement = driver.findElement(by);
    }

然后您只需要将WebDriver包装到RobustWebDriver中,就可以开始了:

WebDriver driver = new RobustWebDriver(new ChromeDriver());

编辑:

当然,您需要自己上下滚动。

答案 1 :(得分:0)

好了,我把头发扯了一天之后,我终于意识到发生了什么事。对我来说应该是显而易见的。单击“下一步”按钮时,加载新页面需要一些时间。通过简单地添加一个延迟,就可以加载新的DOM并开始对其进行处理,而不是对前一个DOM进行处理!

            driver.findElement(By.xpath(".//*[@class='PageRight']")).click();
        try {
            Thread.sleep(4000); //provide some time for the page to load before processing it
        } catch (InterruptedException ex) {
            Logger.getLogger(RealAuction.class.getName()).log(Level.SEVERE, null, ex);
        }

现在它运行到完成,没有StaleElementReferenceException。