如何在ajax按钮的WebElement.click()之后避免StaleElementException?

时间:2012-09-27 10:25:15

标签: java selenium junit selenium-chromedriver

我有一个页面,其中包含一个带有一个输入字段和一个提交按钮的小表单。 提交按钮是AJAX!当我点击它时,它会将输入值提交给服务器,该服务器将其验证为可接受并加载新页面,否则会发现问题并添加反馈错误标签。

我编写了以下selenium代码来测试此功能:

WebDriverWait waiter = new WebDriverWait(driver, 20);
WebElement inputField = waiter.until(ExpectedConditions.presenceOfElementLocated(By.id("inputField")));
inputField.sendKeys("Test input");

WebElement submitInput = waiter.until(ExpectedConditions.presenceOfElementLocated(By.id("submitInput")));
submitInput.click();

WebElement feedback = waiter.until(ExpectedConditions.presenceOfElementLocated(By.id("feedback")));
Assert.assertTrue("Feedback not was not empty, but was: " + feedback.getText(), feedback.getText().isEmpty());

问题是submitInput.click()完成并立即返回,如果新页面是按照the selenium docs通过事件加载的,那么通过“反馈”检索的调用工作正常,我可以通过调试看看它是否正确获取了元素,但是当我调用StaleElementException时,我得到feedback.getText(),因为页面已经在后台刷新。

如何在没有测试的情况下通过测试:

  1. 添加Thread.sleep()。
  2. 添加StaleElementException catch以重新获取元素。
  3. 注意:当输入无效时,上述测试有效,因为页面未刷新,但输入有效时无效。

    非常感谢任何帮助。

    编辑:请注意,在元素出现之前轮询页面不是问题,但是检测submitInput.click()是否加载新页面或是否将文本添加到现有标签是一个问题。

    编辑2:为了说明问题,我已经编译了一个时间记录的贯穿始终交错测试和服务器日志:

    16:34:48,589 - TEST   - Navigating to page.
    16:34:48,605 - SERVER - Page request started.
    16:34:48,631 - SERVER - Page request ended.
    16:34:49,050 - TEST   - Typing test input.
    16:34:49,456 - TEST   - Clicking button.
    16:34:49,519 - SERVER - Received click event.
    16:34:49,529 - SERVER - Responding with redirect.
    16:34:49,556 - TEST   - Finding feedback by id.
    16:34:49,560 - SERVER - Page request started.
    16:34:49,581 - SERVER - Page request ended.
    16:34:49,775 - TEST   - Test threw exception.
    

    您可以看到服务器在从测试发送后半小时内收到点击事件。它立即响应(在这种情况下)一个重定向命令。由于点击是AJAX事件,测试同时continued in the background as per the docs,这导致(如您所见)在服务器收到新页面请求之前0.004秒检索反馈的测试。当feedback.getText()被解析并发送到ChromeDriver时,服务器已经响应了新页面,因此该函数返回过时的异常。

    关键点在于我可以使用Thread.sleep(1000)来解决这个问题,但这远非理想;首先是因为服务器可能需要花费超过半秒的时间来解析AJAX并返回结果,这使得测试不确定,其次因为这些时间很快就会堆积起来并浪费时间,通常不必要时只需要需要几毫秒。

4 个答案:

答案 0 :(得分:2)

我实际上会以其他方式采取行动来解决此问题。 我会使用一些js来克服StaleElementException +++流畅的等待+++ css选择器而不是xpaths。 1)流利的等待方法。它返回实际(位于)的web元素:

public WebElement fluentWait(final By locator){
    Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
            .withTimeout(30, TimeUnit.SECONDS)
            .pollingEvery(5, TimeUnit.SECONDS)
            .ignoring(NoSuchElementException.class);

    WebElement foo = wait.until(
        new Function<WebDriver, WebElement>() {
            public WebElement apply(WebDriver driver) {
                return driver.findElement(locator);
            }
        }
    );
    return  foo;
};

关于流利的等待,您可以获得herehere

2)String cssSelectorInputField =“[id ='inputField']”    String cssSelectorSubmitInput =“[id ='submitInput']”    String cssSelectorFeedback =“[id ='feedback']”

3)现在我给出一些jsMethods模板(我用于我的自动化,这对你来说很有用)

public String jsGetText(String cssSelector){

  JavascriptExecutor js = (JavascriptExecutor) driver;
        StringBuilder stringBuilder = new StringBuilder();

stringBuilder.append("var x = $(\""+cssSelector+"\");");
        stringBuilder.append("return x.text().toString();")       ;


       String res= (String) js.executeScript(stringBuilder.toString());
return res;
}

public String jsGetColor(String css){

        JavascriptExecutor js = (JavascriptExecutor) driver;
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("var x=$(\'"+css+"\');");
        stringBuilder.append("return x.css('color')");
        String res= (String) js.executeScript(stringBuilder.toString());
        return res;

    }

public void jsClickOnElement(String css){

JavascriptExecutor js = (JavascriptExecutor) driver;
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("var x = $(\'"+css+"\');");
        stringBuilder.append("x.click();");
        js.executeScript(stringBuilder.toString());

}

4)所以最后的步骤如下:

driver.findElement(By.cssSelector(cssSelectorInputField)).sendKeys("Test input");
driver.findElement(By.cssSelector(cssSelectorSubmitInput)).click();
//or jsClickOnElement(cssSelectorSubmitInput);
WebElement feedBack =fluentWait(cssSelectorFeedback);
String textForVerifying=feedBack.getText().trim();
//or String textForVerifying= jsGetText(cssSelectorFeedback);
Assert.assertTrue(textForVerifying.equals("..blablablab..."));

希望现在适合你)

答案 1 :(得分:0)

不要忘记

这样的方法
input.findElements(By.xpath("//xpath")).size() > 0


driver.findElement(By.xpath("//xpath")).isDisplayed()

public bool IsElementPresent(By selector)
{
    return driver.FindElements(selector).Any();
}

用于验证页面上的元素是否存在。他们也很有帮助。但请注意您找到的定位器(在firebug中验证您正确定位的所有元素)

答案 2 :(得分:0)

我不确定如何在Java中完成此操作,也不确定我是否正确理解了该方案,但我会尝试。它在Ruby btw ...

 @wait.until { @driver.find_element(:id,"feedback") }
 assert "true","Feedback was not empty but was #{@driver.find_element(:id,\"feedback\").text}"

我基本上要做的是不使用在断言上方的行中找到/存在的web元素“反馈”。相反,我让webdriver再次找到该元素,然后在其上执行了一些操作(获取文本)。

修改


@wait.until { alert_text = @driver.find_element(:id,"feedback").text }
assert_match "expected text","#{alert_text}","Feedback is not empty and expected"

答案 3 :(得分:0)

对于遇到类似问题的人(这是两个不同的问题,1 - 检测到AJAX控制的页面更改,2 - 检测AJAX控制的元素更改),我的解决方案最终是

步骤1:创建一些函数来通过jQuery跟踪页面更改(jQuery用于所有页面,因此脚本执行正常):

private void updateLastChange()
{
    driver.executeScript("$('html').data('CHANGED_PAGE', false)");
}

public boolean pageChanged()
{
    Object script = driver.executeScript("return $('html').data('CHANGED_PAGE');");
    Boolean changed = (Boolean)script;
    if(changed == null)
    {
        updateLastChange();
        return true;
    }
    return false;
}

然后我可以简单地调用pageChanged()来检测页面更改。

第2步:创建一个实现ExpectedCondition<Boolean>的匿名类。

使用上面的示例,问题是反馈是否准确无关,我将问题中的代码的最后两行更改为:

WebDriverWait waiter = new WebDriverWait(driver, 5, 200);
waiter.ignoring(AssertionFailedError.class);
waiter.ignoring(StaleElementReferenceException.class);
waiter.until(new ExpectedCondition<Boolean>()
{
    public Boolean apply(WebDriver input)
    {
        WebElement feedback = input.findElement(By.id("feedback"));
        Assert.assertTrue("Feedback not was not empty, but was: " + feedback.getText(), feedback.getText().isEmpty());            
        return true;
    }
});

这样,它每隔200毫秒轮询一次页面,持续5秒,忽略断言异常和陈旧元素异常。它通过的唯一方法是在5秒钟用完之前达到return true;行。我现在正在考虑如何制作这个整洁的但它效果很好,比简单的睡眠更快,并且由于轮询而提前成功(如果可能的话)。