当我使用WebDriverWait而不是Thread.sleep时,我得到一个StaleElementReferenceException

时间:2014-10-23 17:59:58

标签: selenium groovy selenium-webdriver builder

我正在BuilderSwagger页面写一个Selenium对象。出于本讨论的目的,我的问题代码可以简化为以下内容:

class SwaggerBuilder {

    WebDriver driver
    def resources

    SwaggerBuilder(WebDriver driver) {

        this.driver = driver

        Thread.sleep(2000)

        resources = driver.findElements(By.className("resource")).collectEntries {
            def resourceName = it.findElement(By.tagName("a")).getText().replaceFirst("[/]", "")
            [(resourceName): it]
        }
    }

    Object invokeMethod(String name, Object args) {

        if(resources[(name)] == null)
            throw new NoSuchElementException("Resource $name cannot be found.")

        resources[(name)].findElement(By.linkText("List Operations")).click()
    }
}

调用它非常简单(来自JUnit3):

void test1() {

    driver = new FirefoxDriver()
    driver.get("http://petstore.swagger.wordnik.com/")

    def petstore = new SwaggerBuilder(driver)    // problem does not get past this line!
    try {
        petstore.not_there()
        fail("Did not catch exception.")
    } catch(NoSuchElementException ex) {
        assertTrue(ex.message.startsWith("Resource not_there cannot be found."))
    } catch(Exception ex) {
        fail("Caught wrong exception: ${ex.class}.")
    }
}

构造函数中的Thread.sleep(2000)是一个可怕的眼睛疼痛!我尝试用以下等待替换它:

def wait = new WebDriverWait(driver, 20)
wait.until(ExpectedConditions.visibilityOfElementLocated(By.className("resource")))

或:

    def wait = new FluentWait<By>(By.className("resource")).
            withTimeout(20, TimeUnit.SECONDS).
            pollingEvery(100, TimeUnit.MILLISECONDS).
            ignoring(StaleElementReferenceException)
    wait.until(new Function<By, Boolean>() {
                def count = 0
                @Override
                Boolean apply(By by) {
                    def oldCount = count
                    count = driver.findElements(by).size()
                    return count == oldCount
                }
            })

这两个产生了相同的结果:“org.openqa.selenium.StaleElementReferenceException:元素不再附加到以def resourceName = ...开头的行中的闭包”。

Thread.sleep(2000)是我现在才能完成这项工作的唯一方法。我希望用更加浏览器友好/强大的等待替换它,所以即使对于加载速度低于2秒的页面,这也可以工作。还有其他想法吗?

2 个答案:

答案 0 :(得分:2)

当我加载您的网页时,我会在控制台"Loaded SwaggerUI"中看到三次。这就是你的问题:SwaggerUI加载了3次。

如何计算

所以我这样做了:

  1. 我在打印出"Loaded SwaggerUI"的行上放了一个断点。

  2. 我重新加载。

  3. 当我点击断点时,我拍摄了具有类resource的元素的快照:

    var snap1 = Array.prototype.slice.call(
                    document.getElementsByClassName("resource"))
    

    (您必须将返回的值复制到Array(此处slice),因为getElementsByClassName会返回 live 集合。)

  4. 我点击了调试器的继续按钮。

  5. 当我再次点击断点时,我拍了第二张快照(名为snap2)。

  6. 现在好一些测试。如果DOM没有改变,那么元素应该是相同的:

    > snap1[0] === snap2[0]
    false
    

    看起来不太好看。让我们看看DOM树中还有什么:

    > document.contains(snap1[0])
    false
    > document.contains(snap2[0])
    true
    

    第一个快照中的元素不再在树中,而第二个快照中的元素是。

    为什么Selenium错误?

    2秒的等待足以让Selenium在DOM稳定后开始寻找元素。但是,当您告诉Selenium等到页面中有类resource的可见元素时,它会等到第一次首次加载SwaggerUI 。在处理它首先找到的元素的某个时刻,SwaggerUI 加载另一个时间然后它找到的旧元素不再在DOM树中。所以它引发了StaleElementReferenceException,因为它曾经在DOM树中找到的元素不再存在。它被一个位于相同位置且结构相同的元素所取代,但Selenium希望完全相同的元素不是相同的副本。

答案 1 :(得分:1)

在@Louis进行了大多数优秀的研究/建议之后,我最终使用了:

def wait = new FluentWait<By>(By.className("resource")).
        withTimeout(10, TimeUnit.SECONDS).
        ignoring(NoSuchElementException)
wait.until(new Function<By, Boolean>() {
            WebElement res
            Boolean apply(By by) {
                def oldRes = res
                res = driver.findElement(by)
                return res == oldRes
            }
        })

如果有人有兴趣,entire Builder can be found on SourceForge(在撰写本文时仍在建设中)。