凌晨三点被电话轰醒,老板在群里贴出截图:用户问"昨天我们聊到哪儿了?",AI却像喝了孟婆汤一样回复"请重新描述您的问题"。线上事故,记忆存储丢了。研发查了半天,最后定位到一个冷门的时序竞态:消息顺序错乱导致记忆写入失败。手工测试压根没覆盖多会话交错写入的场景。那晚我边修 bug 边下决心:这套聊天记忆的 E2E 测试,必须整成自动化的,而且要能覆盖所有变态场景。
问题拆解:为什么 AI 记忆存储的测试那么难搞?
AI 对话记忆不是说简单的"存个字段",它的核心流程是:用户 A 说话 → 生成回复的同时提取记忆 → 存入持久化存储(Redis/Postgres) → 下一轮或新会话时召回记忆构建上下文。测试需要模拟:
- 多轮对话中正确提取和更新记忆;
- 跨会话的记忆持久化(关掉浏览器再打开还能记得);
- 并发请求下记忆覆盖、错位;
- 前后端交互中异步写入的时机。
手工测试通常靠人点:在聊天框发几句话,关掉标签页,再打开,看看是否记得。容易漏掉关键路径,而且耗时惊人------一次全量回归要 4 个多小时,还没人敢保证覆盖完整。我们太需要一个能模拟真实浏览器交互、断言精准、还能跑在 CI 里的自动化方案。
方案设计:Playwright + pytest,不选别的理由
我们最终选了 Playwright 作为浏览器驱动,配合 pytest 做用例管理和断言,输出 Allure 报告,整体跑在 GitHub Actions 上。
为什么没选 Selenium?慢。Selenium 需要单独安装 WebDriver,等待机制全靠隐式/显式封装,经常因为网络抖动假失败。Playwright 内置自动等待、网络拦截、trace viewer,调试体验甩几条街。Cypress 呢?它虽然开发体验好,但不支持多标签页和多浏览器并行,而跨会话测试必须开新标签或 browser context,直接被劝退。pytest 则比 Mocha/Jest 更契合 Python 技术栈,参数化、fixture 管理状态顺手。
架构上我们做了三层抽象:
- Page Object:封装聊天页面元素和操作(send_message, get_last_reply...)
- browser fixture:管理 browser context,隔离每个测试的存储状态(storageState)
- memory validator:一个可复用的断言工具,轮询检查记忆召回是否生效,处理异步写入。
核心实现:写出真能跑的记忆存储测试
先解决全局状态和登录态问题------用 conftest.py 提供一个持久化的认证上下文,避免每次用例重复登录。
python
# conftest.py
import pytest
from playwright.sync_api import sync_playwright, BrowserContext
@pytest.fixture(scope="session")
def browser_context() -> BrowserContext:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True) # CI 用 headless
# 从文件加载已保存的登录态,避免重复登录,节省 8 秒/用例
storage_state = "auth.json"
context = browser.new_context(storage_state=storage_state)
yield context
context.close()
browser.close()
第一个关键用例:跨会话记忆召回
这段代码模拟用户在一个会话中说"我叫小明",另一个会话中问"我叫什么",验证 AI 是否真的记住了名字。
python
# test_memory_cross_session.py
from playwright.sync_api import Page, expect
def test_memory_remembered_across_sessions(browser_context):
# 会话 1:建立记忆
page1 = browser_context.new_page()
page1.goto("https://chat.example.com")
page1.fill('[data-testid="chat-input"]', "我叫小明,喜欢喝咖啡")
page1.click('[data-testid="send-btn"]')
# 等待回复出现,证明消息已处理
expect(page1.locator('[data-testid="msg-bubble"]').last).to_contain_text("喜欢喝咖啡", timeout=10000)
# 会话 2:在新 tab 中验证记忆召回
page2 = browser_context.new_page()
page2.goto("https://chat.example.com")
page2.fill('[data-testid="chat-input"]', "你还记得我叫什么吗?")
page2.click('[data-testid="send-btn"]')
# 关键:异步记忆写入可能有延迟,不能直接断言,需要智能等待
expect(page2.locator('[data-testid="msg-bubble"]').last).to_contain_text("小明", timeout=15000)
第二个用例:多轮更新记忆不丢失
这是最初线上出事的场景:连续快速发送消息,记忆提取是否会错乱。
python
def test_rapid_conversation_memory_update(page: Page):
page.goto("https://chat.example.com")
# 模拟用户快速发送三条信息
messages = [
"我今年30岁",
"我住在杭州",
"我的猫叫豆包"
]
for msg in messages:
page.fill('[data-testid="chat-input"]', msg)
page.click('[data-testid="send-btn"]')
# 等待每条消息的服务器回执,避免队列堆积
expect(page.locator('[data-testid="msg-status"]').last).to_have_text("sent", timeout=5000)
# 用一个问题召回全部记忆
page.fill('[data-testid="chat-input"]', "总结一下你记得关于我的所有信息")
page.click('[data-testid="send-btn"]')
last_reply = page.locator('[data-testid="msg-bubble"]').last
# 断言三个关键信息都还存在
expect(last_reply).to_contain_text("30岁")
expect(last_reply).to_contain_text("杭州")
expect(last_reply).to_contain_text("豆包")
踩坑记录:那些文档没教的事
坑 1:聊天框藏在 iframe 里,选择器全废
现象:一切 selector 都报 Timeout 30000ms exceeded,但手动打开 DevTools 明明能找到元素。原因:我们的聊天组件是独立部署,通过 <iframe> 嵌入主站。Playwright 的 page 对象默认只在主文档查找。解决:用 frame_locator 强行切进去。
python
chat_frame = page.frame_locator('#chat-iframe')
chat_frame.fill('[data-testid="chat-input"]', "你好")
官方文档只演示了最常见的单 frame 场景,没提 iframe 里还有 Shadow DOM 时怎么链式定位,这花了我一整个下午。
坑 2:记忆异步写入,新会话"秒查"永远失败
测试脚本开新页面后立刻发问,AI 经常回"我还不知道你的名字"。追踪链路发现:记忆提取服务收到消息后把任务丢入了消息队列,消费者写入存储有 200~800ms 延迟。最初的 page.wait_for_timeout(2000) 硬等既慢又不稳定。后来改用 Playwright 的 expect().to_pass() 做轮询断言,超时设 15 秒,只要记忆完成写入就立刻通过,平均等待仅 400ms,再也没因时序假失败过。
坑 3:CI 中 headless 渲染与本地不一致导致截图对比失效
本地 Mac 跑得好好的视觉回归,到 GitHub Actions Ubuntu 镜像全挂。差异根源是系统字体渲染不同。后来直接放弃像素级截图对比,改为基于 DOM 文本的 to_contain_text 断言,同时保留截图仅当事后复查用,不再作为通过标准。
效果验证:数据不说谎
| 维度 | 手工测试 | 自动化后 |
|---|---|---|
| 单次全量回归耗时 | 4 小时+ | 10 分钟 |
| 用例覆盖率 | 约 20% | 96% |
| 跨会话记忆覆盖 | 0 个用例 | 12 个用例 |
| 假失败率(flaky) | 不适用 | < 3% |
| CI 集成 | 无 | GitHub Actions 每次 PR |
现在每次提交代码后,自动跑 30+ 条记忆存储相关 E2E 用例,3 个月内拦截了 7 次记忆丢失/错位的回归,凌晨告警彻底消失。
直接拿去用的命令
bash
# 克隆 test 套件,一行跑起所有记忆存储用例
git clone https://github.com/yourorg/ai-memory-e2e.git
cd ai-memory-e2e && pip install -r requirements.txt
playwright install chromium
pytest tests/ --browser chromium --headed --alluredir=./allure-results
#Python #Playwright #端到端测试 #AI测试 #自动化测试 #pytest
关于作者
一个专啃 AI 应用层硬骨头的后端/测试架构师,常年出没于凌晨的 CI 日志堆里。
GitHub: github.com/baofugege
Sponsor: github.com/sponsors/ba... --- 如果这篇文章让你今晚少熬两小时,欢迎请我喝杯咖啡
提供服务:Python 后端性能优化 / 自动化测试体系搭建 / 技术咨询,联系 Telegram @baofugege