0

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

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

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()如果通过事件加载新页面,则会立即完成并返回:selenium docs,因此通过“反馈”进行检索的调用工作正常,我可以通过调试看到它正确获取了元素,但是StaleElementException当我打电话时我得到了一个feedback.getText(),因为页面已经在后台刷新了。

我怎样才能让测试通过而没有:

  1. 添加 Thread.sleep()。
  2. 添加一个StaleElementException捕获以重新获取元素。

注意:上述测试在输入无效时有效,因为页面未刷新,但在输入有效时无效。

任何帮助将非常感激。

编辑:请注意,在元素出现之前轮询页面不是问题,而是检测是否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 事件,因此根据文档在后台继续进行测试,这导致(如您所见)测试在服务器收到新页面请求之前 0.004 秒检索反馈。在feedback.getText()解析并发送到 ChromeDriver 时,服务器已经响应了一个新页面,因此该函数返回过时异常。

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

4

4 回答 4

2

我实际上会采取其他方式来解决这个问题。我会使用一些 js 来克服 StaleElementException+++ fluent wait +++ css 选择器而不是 xpaths。1)流利的等待方法。它返回您实际的(定位的)网络元素:

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;
};

关于流利的等待你可以到达这里这里

2) 字符串 cssSelectorInputField = "[id='inputField']" 字符串 cssSelectorSubmitInput = "[id='submitInput']" 字符串 cssSelectorFeedback = "[id='feedback']"

3)现在我提供了一些 jsMethods 模板(我用于我的自动化,prolly 可能对你有用)

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..."));

希望这对你有用)

于 2012-09-27T11:12:23.830 回答
0

不要忘记这样的方法

input.findElements(By.xpath("//xpath")).size() > 0


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

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

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

于 2012-09-27T15:31:06.377 回答
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 秒,忽略断言异常和过时元素异常。它通过的唯一方法return true;是在 5 秒用完之前到达线路。我目前正在考虑如何使它更整洁,但它工作得很好,比简单地睡觉更快,并且由于轮询而提前(如果可能)成功。

于 2012-10-02T15:37:03.737 回答
0

我不确定如何在 Java 中做到这一点,也不确定我是否正确理解了这个场景,但我会尝试一下。顺便说一句,它在Ruby中......

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

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

编辑


@wait.until { alert_text = @driver.find_element(:id,"feedback").text }
assert_match "expected text","#{alert_text}","Feedback is not empty and expected"
于 2012-09-28T02:44:53.683 回答