PAYATHON 2026

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() 代替显式等待。支付完成所需的时间可能波动,固定休眠会拖慢测试,也容易造成不稳定。显式等待会在条件满足后立即继续,并在超时时给出明确的失败结果。

备注:内容仅供参考。