20x Faster RAG Memory Testing: Trade Postman for Playwright + Chroma
Where the Problem Really Gets Stuck
The memory write pipeline of a RAG app is far more complex than a normal API:
- Multi‑step dependency: user asks a question → frontend assembles context → backend calls the LLM and asynchronously writes to the vector store → retrievable in the next conversation.
- State is hard to reproduce: whether the memory is written correctly depends on prompt trimming, the embedding model, and whether the async worker drops any message.
- Verification must cross layers: you can’t just look at an HTTP 200; you must actually find that memory inside Chroma, with the correct content and metadata.
The old workflow: send a few fixed conversations via Postman, then manually search in Chroma’s Jupyter. That has three fatal flaws:
- Incomplete coverage: a human can maintain at most 5 test cases, yet the real-world combinatorial state space is a Cartesian product.
- Slow feedback: one cycle of “change code → restart service → manual test → inspect DB” takes at least 5 minutes. Fixing 3 bugs easily burns half an hour.
- Unrepeatable: being woken up at midnight, you simply cannot reproduce the earlier scenario by hand.
We needed an end‑to‑end automated solution that could drive a real browser through a full conversation and then dive directly into the vector store to inspect memory chunks - like performing a gastroscopy on the RAG.
Why We Ruled Out Other Approaches
Option A: pure API tests (requests + Chroma client)
This can’t reproduce real frontend behavior. The frontend might do extra processing on the context (e.g., filtering short texts, merging system prompts). Pure API tests would miss those paths.
Option B: Selenium + custom assertions
Selenium requires a lot of boilerplate for async waits and network interception. Playwright, on the other hand, was built for modern SPAs - waitForResponse hooks directly into the target API, and traces let you replay DOM snapshots when something fails.
Option C: mock the vector store with a lightweight fake
Tests must hit the real Chroma, because different vector stores handle embedding normalization and index refresh strategies differently. Test with mocks, and you’re likely to have a 50% failure rate in production.
So the choice was clear: Playwright as the browser driver + a direct Chroma client for verification. Everything runs locally or in CI, and a single pytest test_memory.py does the whole job.
Core Implementation: Inserting the Gastroscope Step by Step
1. Preparing the clients: Playwright and Chroma side by side
This snippet creates the browser driver and initializes an embedded Chroma instance whose collection name matches production.
# test_setup.py
import chromadb
from chromadb.config import Settings
from playwright.sync_api import sync_playwright
# 嵌入式 Chroma,数据持久化到 ./chroma_data,和开发环境一致
chroma_client = chromadb.Client(
Settings(
chroma_db_impl="duckdb+parquet",
persist_directory="./chroma_data"
)
)
# 确保 collection 存在(线上用的 "chat_memories")
collection = chroma_client.get_or_create_collection("chat_memories")
def get_browser_page():
"""启动无头浏览器并返回 page 对象"""
playwright = sync_playwright().start()
browser = playwright.chromium.launch(headless=True)
page = browser.new_page()
return page, browser, playwright
2. The core test: simulate a conversation and directly verify the vector store
This test completes a full memory‑storage loop: the user types personal information on the page → the server replies and asynchronously writes to Chroma → the test verifies the correct entry directly in the database.
# test_memory.py
import time
import pytest
from test_setup import collection, get_browser_page
MEMORY_EXTRACTION_TIMEOUT = 5 # 异步写入最多等 5 秒
def test_user_profile_memory():
page, browser, playwright = get_browser_page()
try:
# 1. 打开对话页面
page.goto("http://localhost:3000/chat")
# 2. 输入记忆信息
page.fill("textarea#user-input", "我叫张三,我对花生过敏,记住这个。")
# 3. 拦截 /api/chat 响应,保证 LLM 回复已返回
with page.expect_response(lambda resp: "/api/chat" in resp.url and resp.status == 200):
page.click("button#send")
# 4. 等待异步写入完成(简单轮询)
start = time.time()
while time.time() - start < MEMORY_EXTRACTION_TIMEOUT:
# 重新获取集合(踩坑点:不 refresh 查不到最新数据)
fresh_collection = chroma_client.get_collection("chat_memories")
results = fresh_collection.query(
query_texts=["花生过敏"],
n_results=1,
where={"user_id": "张三"}
)
# Here, the test would first see the result for “peanut allergy” in memory; you can then add assertions to check that the content matches expectations.
finally:
browser.close()
playwright.stop()
The entire process is fully automated, and a full regression run takes just a few seconds.
Comments
No comments yet. Start the discussion.