Selenium + Java:如何等待元素隐藏并断言支付结果?
结论
问题多半在 CSS 选择器,而非等待时间。
.poll-loader-spinner .ng-scope 中的空格表示查找 .poll-loader-spinner 内部 class 为 .ng-scope 的后代元素。如果这两个 class 在同一个元素上,选择器应写成:
.poll-loader-spinner.ng-scope
等待 loading 元素时,应检查它是否已经不可见,而不是只等 ng-hide class 出现。对于支付结果,可以分别定位成功和失败提示,等待任意一条显示后再断言。如果可以修改页面,最好为这些状态元素添加稳定的 id 或 data-testid。
正确等待 loading 元素隐藏
以 Selenium 4 为例:
WebDriverWait wait = new WebDriverWait(
getDriver(),
Duration.ofSeconds(60)
);
By loader = By.cssSelector(".poll-loader-spinner.ng-scope");
wait.until(ExpectedConditions.invisibilityOfElementLocated(loader));
出现以下任一情况时,invisibilityOfElementLocated 都会结束等待:
- 元素存在,但
isDisplayed()返回false - 元素已从 DOM 中删除
- 元素暂时不存在
这里的 60 秒是最长等待时间,并不意味着每次都会等满 60 秒。只要条件满足,程序就会立即继续执行。
如果实际 HTML 如下:
<div class="poll-loader-spinner ng-scope">
Loading...
</div>
正确的选择器就是 .poll-loader-spinner.ng-scope。
只有 HTML 确实是以下嵌套结构时,原选择器中的空格才是正确的:
<div class="poll-loader-spinner">
<span class="ng-scope">Loading...</span>
</div>
直接检查元素是否不可见
如果页面的状态切换比较复杂,可以使用自定义等待,直接检查 isDisplayed():
By loader = By.cssSelector(".poll-loader-spinner.ng-scope");
wait.until(driver -> {
List<WebElement> elements = driver.findElements(loader);
return elements.isEmpty()
|| elements.stream().noneMatch(WebElement::isDisplayed);
});
这种写法可以兼容两种情况:
- Angular 为元素添加
ng-hide,再通过 CSS 将其隐藏 - Angular 直接从 DOM 中移除元素
不建议只检查 class 字符串。虽然 ng-hide 通常会隐藏元素,但最终效果仍由页面实际加载的 CSS 决定。isDisplayed() 判断的是元素是否真正显示,更接近用户在浏览器中看到的状态。
等待成功或失败结果出现
假设页面上的两条提示可以通过稳定的 class 定位:
By successMessage = By.cssSelector(".payment-success");
By errorMessage = By.cssSelector(".payment-error");
wait.until(ExpectedConditions.or(
ExpectedConditions.visibilityOfElementLocated(successMessage),
ExpectedConditions.visibilityOfElementLocated(errorMessage)
));
等待结束后,再检查哪一条提示可见:
List<WebElement> successElements = getDriver().findElements(successMessage);
List<WebElement> errorElements = getDriver().findElements(errorMessage);
boolean successVisible = successElements.stream().anyMatch(WebElement::isDisplayed);
boolean errorVisible = errorElements.stream().anyMatch(WebElement::isDisplayed);
Assertions.assertTrue(
successVisible || errorVisible,
"等待结束后没有发现可见的支付结果"
);
Assertions.assertFalse(
successVisible && errorVisible,
"成功和失败提示不应同时可见"
);
如果当前测试预期支付成功,可以继续断言成功文案:
WebElement message = wait.until(
ExpectedConditions.visibilityOfElementLocated(successMessage)
);
Assertions.assertEquals(
"Congrats! Your course has been successfully paid for!",
message.getText().trim()
);
如果预期支付失败,则检查失败提示:
WebElement message = wait.until(
ExpectedConditions.visibilityOfElementLocated(errorMessage)
);
Assertions.assertEquals(
"I'm sorry, there seems to be an error.",
message.getText().trim()
);
getText() 会返回元素及其可见子元素呈现的文本。调用 trim() 可以去除首尾空白,避免 HTML 缩进影响断言。
为状态元素添加稳定标识
如果可以修改页面,尽量不要依赖 Angular 自动生成的 ng-scope、ng-hide 等 class。它们属于框架的实现细节,页面升级后可能发生变化。
可以将 HTML 写成:
<div data-testid="payment-loader">Loading...</div>
<p data-testid="payment-success">
Congrats! Your course has been successfully paid for!
</p>
<p data-testid="payment-error">
I'm sorry, there seems to be an error.
</p>
对应的定位方式是:
By loader = By.cssSelector("[data-testid='payment-loader']");
By successMessage = By.cssSelector("[data-testid='payment-success']");
By errorMessage = By.cssSelector("[data-testid='payment-error']");
也可以使用唯一的 id:
By loader = By.id("payment-loader");
By successMessage = By.id("payment-success");
By errorMessage = By.id("payment-error");
无论选择 id 还是 data-testid,都不需要 XPath,定位意图也比按文本或框架 class 查找更清楚。
一个完整的成功支付示例
WebDriverWait wait = new WebDriverWait(
getDriver(),
Duration.ofSeconds(60)
);
By loader = By.id("payment-loader");
By successMessage = By.id("payment-success");
By errorMessage = By.id("payment-error");
wait.until(ExpectedConditions.invisibilityOfElementLocated(loader));
wait.until(ExpectedConditions.or(
ExpectedConditions.visibilityOfElementLocated(successMessage),
ExpectedConditions.visibilityOfElementLocated(errorMessage)
));
boolean errorVisible = getDriver()
.findElements(errorMessage)
.stream()
.anyMatch(WebElement::isDisplayed);
Assertions.assertFalse(
errorVisible,
"支付失败提示出现在页面上"
);
WebElement success = wait.until(
ExpectedConditions.visibilityOfElementLocated(successMessage)
);
Assertions.assertEquals(
"Congrats! Your course has been successfully paid for!",
success.getText().trim()
);
不要用 Thread.sleep() 代替显式等待。支付完成所需的时间可能波动,固定休眠会拖慢测试,也容易造成不稳定。显式等待会在条件满足后立即继续,并在超时时给出明确的失败结果。
备注:内容仅供参考。
