为什么你的 Selenium 测试在 AI Chatbots 上会失败(以及如何修复)

发布: (2025年12月14日 GMT+8 05:14)
5 min read
原文: Dev.to

Source: Dev.to

你将学到

  • 问题所在:为什么 WebDriverWait 在流式响应时会失效
  • MutationObserver:在浏览器中实现零轮询的流检测
  • 语义断言:使用机器学习对非确定性输出进行验证
  • TTFT 监控:测量大语言模型的首令牌时间(Time‑To‑First‑Token)

根本的不兼容性

传统的 Selenium WebDriver 测试假设页面是静态的,内容一次加载后保持不变。AI 聊天机器人以两种方式打破了这一假设:

  1. 流式响应 – 令牌会在 2–5 秒内逐个到达。WebDriverWait 常常在第一个令牌出现时就触发,只捕获到部分文本。
  2. 非确定性输出 – 同一个问题可能得到不同(但等价)的答案,导致精确字符串断言失效。
User: "Hello"
AI Response (Streaming):
  t=0ms:    "H"
  t=50ms:   "Hello"
  t=100ms:  "Hello! How"
  t=200ms:  "Hello! How can I"
  t=500ms:  "Hello! How can I help you today?"  ← FINAL

Standard Selenium captures: "Hello! How can I"  ← PARTIAL (FAIL!)

常见的“黑科技”(以及它们为何失效)

黑科技失效原因
time.sleep(5)随意设定;太短会导致不稳定,太长会拖慢 CI
text_to_be_present在第一次匹配时就触发,遗漏完整响应
使用长度检查进行轮询竞争条件;长度可能在流式过程中停滞
精确字符串断言对于非确定性的 AI 输出来说根本不可能

真正的代价

团队大约 30 % 的测试时间 用于调试不稳定的 AI 测试,而不是提升覆盖率。

解决方案:浏览器原生流检测

浏览器能够感知流式何时结束。通过使用 MutationObserver API,我们可以直接在 JavaScript 中监听 DOM 变化,省去 Python 轮询和随意的 sleep。

from selenium_chatbot_test import StreamWaiter
from selenium.webdriver.common.by import By

# 等待 AI 响应完成流式输出
waiter = StreamWaiter(driver, (By.ID, "chat-response"))
response_text = waiter.wait_for_stable_text(
    silence_timeout=500,      # 在 500 ms 无变化后视为“完成”
    overall_timeout=30000   # 最大等待时间
)

StreamWaiter 会注入一个 MutationObserver,在每次 DOM 变动时重置计时器。只有当计时器在没有中断的情况下达到 silence_timeout,才会返回,从而保证获取完整响应。

语义断言:测试意义而非文字

捕获完整响应后,使用语义相似度而不是精确字符串进行比较。

from selenium_chatbot_test import SemanticAssert

asserter = SemanticAssert()

expected = "Hello! How can I help you today?"
actual = "Hi there! What can I assist you with?"

asserter.assert_similar(
    expected,
    actual,
    threshold=0.7  # 需要 70 % 以上的语义相似度
)
# ✅ 通过 – 两者表达了相同的意图

该库使用 sentence-transformersall-MiniLM-L6-v2 模型生成嵌入并计算余弦相似度。模型在首次使用时懒加载,并在 CPU 上运行,CI 中无需 GPU。

TTFT:你未监控的 LLM 性能指标

Time‑To‑First‑Token (TTFT) 对用户体验至关重要。若聊天机器人在 3 秒后才开始回应,即使整体响应时间尚可,也会让人感觉出问题。大多数团队对该指标毫无可视化。

from selenium_chatbot_test import LatencyMonitor
from selenium.webdriver.common.by import By

with LatencyMonitor(driver, (By.ID, "chat-response")) as monitor:
    send_button.click()
    # ... 等待响应 ...

print(f"TTFT: {monitor.metrics.ttft_ms} ms")       # 例如 41.7 ms
print(f"Total: {monitor.metrics.total_ms} ms")   # 例如 2434.8 ms
print(f"Tokens: {monitor.metrics.token_count}") # 例如 48 次变更

实际演示结果

  • TTFT:41.7 ms
  • 总耗时:2.4 s
  • 语义准确率:71 %

综合运用

使用传统 Selenium 无法实现的完整测试示例:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium_chatbot_test import StreamWaiter, SemanticAssert, LatencyMonitor

def test_chatbot_greeting():
    driver = webdriver.Chrome()
    driver.get("https://my-chatbot.com")

    # 输入消息
    input_box = driver.find_element(By.ID, "chat-input")
    input_box.send_keys("Hello!")

    # 在等待响应的同时监控延迟
    with LatencyMonitor(driver, (By.ID, "response")) as monitor:
        driver.find_element(By.ID, "send-btn").click()

        # 等待流式完成(不使用 time.sleep!)
        waiter = StreamWaiter(driver, (By.ID, "response"))
        response = waiter.wait_for_stable_text(silence_timeout=500)

    # 断言语义意义,而非精确文字
    asserter = SemanticAssert()
    asserter.assert_similar(
        "Hello! How can I help you today?",
        response,
        threshold=0.7
    )

    # 验证性能 SLA
    assert monitor.metrics.ttft_ms
  • GitHub:
Back to Blog

相关文章

阅读更多 »