我正在Builder为Swagger页面写一个Selenium对象groovy。出于本讨论的目的,我的问题代码可以简化为以下内容:
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秒的页面,这也可以工作。还有其他想法吗?
答案 0 :(得分:2)
当我加载您的网页时,我会在控制台"Loaded SwaggerUI"
中看到三次。这就是你的问题:SwaggerUI加载了3次。
所以我这样做了:
我在打印出"Loaded SwaggerUI"
的行上放了一个断点。
我重新加载。
当我点击断点时,我拍摄了具有类resource
的元素的快照:
var snap1 = Array.prototype.slice.call(
document.getElementsByClassName("resource"))
(您必须将返回的值复制到Array
(此处slice
),因为getElementsByClassName
会返回 live 集合。)
我点击了调试器的继续按钮。
当我再次点击断点时,我拍了第二张快照(名为snap2
)。
现在好一些测试。如果DOM没有改变,那么元素应该是相同的:
> snap1[0] === snap2[0]
false
看起来不太好看。让我们看看DOM树中还有什么:
> document.contains(snap1[0])
false
> document.contains(snap2[0])
true
第一个快照中的元素不再在树中,而第二个快照中的元素是。
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(在撰写本文时仍在建设中)。