企业内网部署本地 AI QA 工程师:Ollama + LibreChat + Playwright MCP 实战
目标:在一台企业内网 Linux 主机上,部署一套由 LibreChat、Ollama、Qwen3:8b 与 Playwright MCP 组成的浏览器测试助手。第一阶段只访问测试环境,只执行低风险、可回滚的 QA 操作,不接入生产写权限。
结论先行
这套方案适合做探索性测试、测试步骤验证和用例草拟,不适合替代确定性的 Playwright 回归测试流水线。正确的落地方式不是一开始就连接生产系统、Jira 和 GitHub,而是先在隔离测试环境里完成一个最小闭环:
- 用户在 LibreChat 中提交明确的测试步骤;
- Qwen3:8b 通过 Ollama 在内网完成推理;
- Playwright MCP 在隔离浏览器中逐步操作测试站点;
- 助手返回步骤数、通过/失败状态与证据位置;
- 人工复核结果,再决定是否将稳定流程改写成正式 Playwright 测试。
原文在 16 GB RAM、无独立 GPU 的环境中,简单场景启动约需 1 分钟、完整执行约需 4–5 分钟。因此,首轮试点应以隐私、可审计和探索效率为目标,而不是追求比静态脚本更快。
来源与内容边界
- 原文:*Build Your Own Local AI QA Engineer With Docker, Ollama, LibreChat, and Playwright MCP*
- 来源:DZone,2026-07-30;页面注明经 Faisal Khatri 授权发布
- 原文事实:Docker、Ollama、Qwen3:8b、LibreChat、Playwright MCP 的组合架构、基础配置、Agent 指令和两个测试示例。
- 本文提炼:把原文步骤整理成单机内网部署闭环,并补充验收标准与失败处理。
- 实践扩展:容器网络隔离、当前 Streamable HTTP 配置、镜像与模型离线转运、权限边界、审计和四周试点计划。这些是面向企业内网的补充,不是原文结论。
1. 适用范围与明确限制
适用
- 内部测试站点的页面巡检、表单验证和探索性测试;
- 将自然语言测试步骤快速验证后,再沉淀为正式自动化脚本;
- 数据不允许发送到公有云模型的团队;
- 小规模、低并发的 QA 试点。
暂不适用
- 生产环境写操作、支付、审批、删改数据;
- 无人工复核的持续自治测试;
- 需要秒级反馈的大规模回归测试;
- 仅凭 Agent 最终回答就判定测试通过;
- 将 Playwright MCP 当作网络或权限安全边界。其官方项目明确说明:MCP 服务本身不是安全边界。
2. 最小可用架构
内网 QA 用户
│ HTTPS / 内网统一认证
▼
反向代理或内网入口
│
▼
LibreChat :3080
├── OpenAI-compatible API ──> Ollama :11434 ──> Qwen3:8b
└── Streamable HTTP MCP ───> Playwright MCP :8931 ──> Headless Chromium
│
▼
仅允许访问测试域名部署原则:
- 只把 LibreChat 的入口开放给内网用户;
- Ollama 的
11434和 Playwright MCP 的8931只在 Docker 内部网络暴露,不映射到宿主机; - 第一阶段不挂载宿主机业务目录,不提供 GitHub/Jira/数据库写工具;
- 浏览器使用隔离会话,不保存个人登录态;
- 测试账号使用最小权限,禁止复用生产账号。
3. 前置条件
建议基线:
- Linux x86_64 或 ARM64 主机;
- 16 GB RAM 起步,推荐 32 GB;
- 25 GB 以上可用 SSD 空间;
- Docker Engine 与 Docker Compose v2;
- Git;
- 能在准备阶段访问批准的镜像仓库和模型源,或已完成离线转运;
- 一个非生产测试站点及专用测试账号。
检查环境:
docker version
docker compose version
git --version
free -h
df -h验收:以上命令均成功;可用内存和磁盘达到团队基线。
失败处理:若主机不足 16 GB RAM,不要直接扩大并发或上下文长度;先改用更小模型,或换到容量更高的试点主机。
4. 获取 LibreChat 并固定版本
sudo install -d -m 0750 /opt/local-ai-qa
sudo chown "$USER":"$USER" /opt/local-ai-qa
cd /opt/local-ai-qa
git clone https://github.com/danny-avila/LibreChat.git
cd LibreChat
cp .env.example .env
cp librechat.example.yaml librechat.yaml
cp docker-compose.override.yml.example docker-compose.override.yml不要长期跟随浮动的 main、latest 或 @latest。试点验证通过后,应记录并固定:
git rev-parse HEAD
docker compose config --images将 Git 提交号、镜像 digest、Qwen 模型 digest 写入内部变更记录。升级时重新做连通性与三个测试场景,而不是直接覆盖生产试点。
5. 配置内网服务
5.1 librechat.yaml
保留 LibreChat 示例文件中团队需要的其他配置,并合并下面的核心块:
version: 1.3.13
cache: true
endpoints:
custom:
- name: "Ollama"
apiKey: "ollama"
baseURL: "http://ollama:11434/v1/"
models:
default:
- "qwen3:8b"
fetch: true
titleConvo: true
titleModel: "current_model"
summarize: false
summaryModel: "current_model"
modelDisplayLabel: "Ollama"
mcpSettings:
allowedAddresses:
- "playwright-mcp:8931"
mcpServers:
playwright:
type: streamable-http
url: "http://playwright-mcp:8931/mcp"
timeout: 120000
initTimeout: 30000
requiresOAuth: false
serverInstructions: |
仅访问管理员批准的测试域名。
每次只执行一个浏览器工具,等待结果后再继续。
导航后重新获取页面快照,不复用旧页面状态。
不下载文件,不访问宿主机文件,不进行生产写操作。
失败时停止新动作,返回当前 URL、最后成功步骤和失败原因。为什么使用 streamable-http:当前 LibreChat 文档已经支持 streamable-http,Playwright MCP 也提供 /mcp 服务端点;原文采用的 /sse 是兼容旧客户端的路径,不应作为新部署的默认选择。
5.2 docker-compose.override.yml
services:
api:
volumes:
- ./librechat.yaml:/app/librechat.yaml:ro
depends_on:
- ollama
- playwright-mcp
ollama:
image: ollama/ollama:latest
restart: unless-stopped
volumes:
- ollama-data:/root/.ollama
expose:
- "11434"
environment:
OLLAMA_NO_CLOUD: "1"
OLLAMA_CONTEXT_LENGTH: "8192"
OLLAMA_NUM_PARALLEL: "1"
OLLAMA_MAX_QUEUE: "8"
playwright-mcp:
image: mcr.microsoft.com/playwright/mcp:latest
restart: unless-stopped
entrypoint: ["node", "/app/cli.js"]
command:
- "--headless"
- "--browser"
- "chromium"
- "--no-sandbox"
- "--host"
- "0.0.0.0"
- "--port"
- "8931"
- "--allowed-hosts"
- "playwright-mcp:8931"
- "--isolated"
- "--block-service-workers"
- "--output-mode"
- "file"
- "--output-dir"
- "/tmp/playwright-output"
- "--timeout-action"
- "10000"
- "--timeout-navigation"
- "60000"
expose:
- "8931"
tmpfs:
- /tmp:size=1g,mode=1777
security_opt:
- no-new-privileges:true
volumes:
ollama-data:注意:
- 这里的
latest只用于首次试装。通过验收后必须改成内部批准的不可变 tag 或 digest。 --no-sandbox是官方容器长驻示例使用的参数,但它削弱了浏览器进程隔离,因此容器和主机网络仍要视为真正的安全边界。- 不要为
ollama或playwright-mcp添加宿主机ports;expose只供 Compose 内部网络使用。 - 若企业要求 GPU 推理,应按批准的 NVIDIA Container Toolkit 方案单独评估;不要在 CPU 闭环尚未通过时同时引入 GPU 变量。
6. 静态检查与启动
先渲染最终 Compose 配置:
cd /opt/local-ai-qa/LibreChat
docker compose config > /tmp/local-ai-qa-compose.rendered.yaml确认敏感服务没有发布到宿主机:
if grep -Eq 'published: (11434|8931)' /tmp/local-ai-qa-compose.rendered.yaml; then
echo "FAIL: Ollama 或 Playwright MCP 被发布到宿主机"
exit 1
fi
echo "PASS: 11434/8931 未发布到宿主机"启动服务:
docker compose up -d
docker compose ps预期:LibreChat 相关容器、ollama 和 playwright-mcp 均处于 Up;不应出现反复重启。
失败处理:
docker compose logs --tail=200 api
docker compose logs --tail=200 ollama
docker compose logs --tail=200 playwright-mcp先修复第一个明确错误,不要通过无限增大 timeout 掩盖 DNS、模型缺失或 MCP 初始化失败。
7. 下载模型并验证推理
docker compose exec ollama ollama pull qwen3:8b
docker compose exec ollama ollama list
docker compose exec ollama ollama ps从 LibreChat API 容器验证模型 API:
docker compose exec api node -e '
fetch("http://ollama:11434/api/tags")
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
.then(x => {
const names = (x.models || []).map(m => m.name);
if (!names.some(n => n.startsWith("qwen3:8b"))) throw new Error("qwen3:8b missing");
console.log("PASS", names);
})
.catch(e => { console.error("FAIL", e.message); process.exit(1); });
'预期输出形态:
PASS [ 'qwen3:8b' ]然后登录 LibreChat,选择 Ollama > qwen3:8b,输入:
只回答数字:2 + 2 等于多少?验收:回答为 4;容器日志中没有外部模型 API 请求。
8. 验证 Playwright MCP 连接
从 API 容器检查 MCP 端点可达性:
docker compose exec api node -e '
fetch("http://playwright-mcp:8931/mcp", { method: "GET" })
.then(r => {
console.log("HTTP", r.status, "content-type", r.headers.get("content-type"));
if (r.status >= 500) process.exit(1);
})
.catch(e => { console.error("FAIL", e.message); process.exit(1); });
'这里的 GET 不一定完成 MCP 会话初始化;它只验证容器 DNS、端口和 HTTP 服务是否可达。真正的 MCP 协议验收应在 LibreChat 中完成:
- 重启 API:
docker compose restart api; - 登录 LibreChat;
- 创建 Agent,模型选择
Qwen3:8b; - 为 Agent 启用
playwrightMCP; - 保存后确认工具列表中出现 Playwright 浏览器工具。
失败处理:
ENOTFOUND playwright-mcp:检查两个服务是否处于同一 Compose 网络;ECONNREFUSED:检查 MCP 容器日志与--host 0.0.0.0;403或 Host 校验失败:确认客户端使用playwright-mcp:8931,并与--allowed-hosts完全一致;/mcp不兼容:核对实际固定的 Playwright MCP 与 LibreChat 版本;只有在版本证据确认必须兼容旧协议时,才回退到type: sse与/sse。
9. 配置 QA Agent 行为约束
将下面内容放入 LibreChat Agent 的 Instructions。它保留了原文的单步调用、导航后刷新状态、语义定位和失败停止原则,并增加企业内网边界。
# 内网 QA Agent 执行规范
你是只操作测试环境的 QA 自动化助手。
## 范围
- 只执行用户明确列出的步骤,不新增业务动作。
- 只访问允许的测试域名;遇到其他域名立即停止。
- 禁止生产环境、支付、删除、审批、权限变更和批量写入。
## 工具调用
- 每次只调用一个 Playwright 工具。
- 等待当前工具返回后,再决定下一步。
- 任何交互前先获取当前页面的新快照,不猜测页面状态。
## 导航
- 点击链接、登录、提交、继续、保存或任何可能改变 URL 的动作后,等待页面加载和 URL 稳定。
- 导航后必须重新获取快照;不得继续使用旧页面引用。
## 定位
- 优先使用 role、label、text、placeholder、title、testid 等语义定位。
- 不使用 XPath、自动生成类名或 nth(),除非页面不存在更稳定的唯一标识。
- 交互前确认目标元素存在且唯一。
## 失败处理
- 任一工具失败后停止新动作。
- 重新检查当前 URL 和页面快照。
- 未确认页面状态前,不重复点击或提交。
- 最多重试一次;第二次失败后返回失败报告。
## 输出
- 返回:测试名称、总步骤数、逐步 PASS/FAIL、最后 URL、失败原因、证据文件名。
- 不能从最终页面直接证明的结果,标记为“未验证”,不得推测通过。建议参数:Temperature 0.2、Top P 0.85。这些是原文试用参数,不是普适最优值;团队应依据固定测试集比较后再调整。
10. 三个试点场景
所有场景都应指向团队自己的隔离测试域名。下面用 https://qa.example.internal 作为占位符。
场景 A:只读页面巡检
目标:验证模型能正确导航、读取标题并停止,不产生写操作。
输入:
测试名称:首页标题巡检
允许域名:qa.example.internal
步骤:
1. 打开 https://qa.example.internal/
2. 验证页面标题包含“测试环境”
3. 返回当前 URL 和验证结果
限制:不要点击任何按钮或链接预期输出形态:
测试名称:首页标题巡检
总步骤数:3
1. PASS - 已打开允许域名
2. PASS - 标题包含“测试环境”
3. PASS - 当前 URL: https://qa.example.internal/
结论:PASS验收:访问域名正确、没有额外点击、结果包含明确证据而不是泛泛地说“完成”。
场景 B:测试账号登录
目标:验证表单定位、一次提交和登录后页面校验。
前置数据:由管理员创建无生产权限的测试账号;密码通过 LibreChat 的受控用户输入提供,不写入 Agent 指令、Markdown、日志或 Git。
输入:
测试名称:测试账号登录
允许域名:qa.example.internal
步骤:
1. 打开 https://qa.example.internal/login
2. 使用标签定位“用户名”和“密码”字段
3. 填入本次会话提供的测试凭据
4. 点击一次“登录”
5. 等待导航完成并重新获取页面快照
6. 验证页面出现“测试账户概览”
限制:不要修改账户资料;登录失败后不要重复提交验收:
- 只提交一次;
- 导航后重新获取页面状态;
- 成功时明确验证目标文本;
- 失败时停止,不循环尝试密码。
失败预期:若凭据错误,应返回 FAIL - 登录失败,未重试,而不是尝试猜测或更换账号。
场景 C:受控失败与恢复
目标:验证元素缺失时 Agent 会停止并报告,而不是幻觉点击。
输入:
测试名称:缺失元素处理
允许域名:qa.example.internal
步骤:
1. 打开 https://qa.example.internal/
2. 查找文本为“这个按钮不存在”的按钮
3. 如果不存在,停止测试并返回当前 URL、页面标题和失败原因
限制:不要点击其他相似按钮,不要使用 nth() 猜测目标预期输出形态:
测试名称:缺失元素处理
总步骤数:3
1. PASS - 页面已打开
2. FAIL - 未找到唯一且可见的“这个按钮不存在”按钮
3. PASS - 已停止,没有执行替代点击
结论:FAIL(符合失败处理预期)验收:没有替代点击、没有重复查找循环、没有把“找不到元素”改写成成功。
11. 一键预检脚本
创建 /opt/local-ai-qa/LibreChat/scripts/preflight-local-ai-qa.sh:
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
docker compose config >/tmp/local-ai-qa-compose.rendered.yaml
if grep -Eq 'published: (11434|8931)' /tmp/local-ai-qa-compose.rendered.yaml; then
echo "FAIL: sensitive service port published to host"
exit 1
fi
docker compose ps --status running
docker compose exec -T api node -e '
Promise.all([
fetch("http://ollama:11434/api/tags").then(r => {
if (!r.ok) throw new Error(`ollama HTTP ${r.status}`);
return r.json();
}),
fetch("http://playwright-mcp:8931/mcp").then(r => {
if (r.status >= 500) throw new Error(`mcp HTTP ${r.status}`);
return r.status;
})
]).then(([tags, mcpStatus]) => {
const names = (tags.models || []).map(m => m.name);
if (!names.some(n => n.startsWith("qwen3:8b"))) {
throw new Error("qwen3:8b missing");
}
console.log(JSON.stringify({
status: "PASS",
model: "qwen3:8b",
mcp_http_status: mcpStatus
}));
}).catch(e => {
console.error(JSON.stringify({status: "FAIL", error: e.message}));
process.exit(1);
});
'授权并运行:
chmod 0750 scripts/preflight-local-ai-qa.sh
./scripts/preflight-local-ai-qa.sh预期输出形态:
{"status":"PASS","model":"qwen3:8b","mcp_http_status":406}mcp_http_status 的具体非 5xx 状态可能随协议版本变化;预检只证明 HTTP 服务可达,不能替代 LibreChat 中的真实工具调用验收。
12. 内网安全与合规清单
上线试点前逐项确认:
- 镜像来自企业批准仓库,并记录 digest;
- 模型文件来自批准来源,并记录模型名称、大小与 digest;
- Ollama 已设置
OLLAMA_NO_CLOUD=1; 11434、8931未发布到宿主机或办公网;- LibreChat 入口通过内网 TLS、统一认证或等效访问控制保护;
- 测试账号与生产账号隔离;
- 防火墙或代理只允许浏览器访问批准的测试域名;
- 第一阶段没有 GitHub、Jira、文件系统、数据库写工具;
- 不在 Prompt、Agent 指令、代码仓库和普通日志中保存密码或 Token;
- 每次执行保留步骤状态、最终 URL、失败原因和证据文件名;
- 任何生产写操作都必须另行评审,并加入人工确认与回滚;
- 已验证容器删除、模型回滚和配置恢复流程。
特别注意:Playwright MCP 的 --allowed-hosts 用于服务端 Host/DNS rebinding 检查,不是浏览器访问目标的完整网络隔离。真正的目标域名限制应由防火墙、出站代理、网络命名空间或专用测试网段执行。
13. 无互联网内网的离线转运
在可联网的受控准备机上:
docker pull ollama/ollama:latest
docker pull mcr.microsoft.com/playwright/mcp:latest
# LibreChat 仓库 compose 所需镜像也需先拉取:
docker compose pull
docker save -o local-ai-qa-images.tar \
ollama/ollama:latest \
mcr.microsoft.com/playwright/mcp:latest
sha256sum local-ai-qa-images.tar > local-ai-qa-images.tar.sha256模型转运建议使用企业批准的内部模型仓库。若只能转移 Ollama 数据卷,应在准备机完成 ollama pull qwen3:8b,停止写入后导出数据卷,并在目标机恢复到新建卷;具体命令要根据企业备份工具和 Docker 数据目录制定,不要直接复制运行中的卷。
在内网目标机至少执行:
sha256sum -c local-ai-qa-images.tar.sha256
docker load -i local-ai-qa-images.tar验收:校验和一致;目标机能够在无公网访问条件下启动镜像、列出 qwen3:8b 并通过预检。
14. 四周试点计划
第 1 周:单机闭环
- 完成容器部署与端口隔离;
- 跑通场景 A、B、C;
- 记录首次响应时间、总耗时、工具调用次数和失败类型;
- 不接入任何写工具。
第 2 周:小组试用
- 选 3–5 名 QA;
- 使用固定的 10–20 个测试任务;
- 对比 Agent 结果与人工结果;
- 统计误点击、重复提交、元素幻觉和超时。
第 3 周:固化边界
- 固定镜像与模型 digest;
- 固化允许域名、测试账号权限和日志保留策略;
- 将高频且稳定的测试转写成普通 Playwright 测试,不让 Agent 每次重新推理;
- 完成回滚演练。
第 4 周:有限扩展
- 扩大测试页面范围,但不扩大生产权限;
- 只有在审计证据充分时,才评估只读 Jira 或只读 GitHub 工具;
- 不通过“答案看起来正确”判断工具调用可靠性;必须核查工具轨迹和页面证据。
15. 验收标准
试点最低通过线:
- 部署:所有容器稳定运行,敏感端口未对宿主机发布;
- 推理:Qwen3:8b 可在内网调用,Ollama 云功能关闭;
- 工具:LibreChat 能连接 Playwright MCP,并完成真实浏览器调用;
- 行为:场景 A、B 正常完成,场景 C 按预期失败并停止;
- 安全:无生产凭据、无生产写权限、无未批准公网出口;
- 证据:每次运行能给出步骤级 PASS/FAIL、最终 URL 和失败原因;
- 性能:团队接受实测延迟;若不能接受,优先调整模型、硬件或场景,不把探索性 Agent 当静态回归测试替代品。
16. 回滚
停止并保留数据用于排查:
cd /opt/local-ai-qa/LibreChat
docker compose down回滚配置:
git status
git diff -- librechat.yaml docker-compose.override.yml
git checkout -- librechat.yaml docker-compose.override.yml若这些文件是企业自有配置,不应直接 git checkout;应从已评审备份或配置仓库恢复。确认不再需要数据后,删除模型卷属于破坏性操作,必须走审批,不要在普通故障处理中执行 docker compose down -v。
17. 原文方法覆盖清单
| 原文方法 | 是否纳入 | 落点 | 若遗漏原因 |
|---|---|---|---|
| Docker 部署 LibreChat | 是 | 第 4–6 节 | — |
| Ollama 安装/服务与 Qwen3:8b | 是 | 第 5、7 节 | 改为 Compose 内部服务,避免内网暴露 API |
| LibreChat 连接 Ollama | 是 | 第 5.1 节 | — |
| Playwright MCP 服务 | 是 | 第 5.2、8 节 | 使用当前 Streamable HTTP;保留 SSE 回退说明 |
| MCP 允许地址配置 | 是 | 第 5.1 节 | 使用当前 allowedAddresses 精确地址规则 |
| 创建 LibreChat Agent | 是 | 第 8、9 节 | — |
| 单工具串行、导航后刷新、语义定位、失败停止 | 是 | 第 9 节 | — |
| Playwright 首页验证 | 是 | 场景 A | 替换为企业测试域名占位符 |
| ParaBank 登录示例 | 是,改写 | 场景 B | 改为企业测试账号,避免依赖公网演示站 |
| Jira/GitHub/数据库扩展 | 暂不纳入执行 | 第 1、14 节 | 首阶段只读低风险试点,不增加写权限 |
| macOS Homebrew 安装 Ollama | 否 | — | 本教程目标为企业内网 Linux 容器部署 |
旧版 SSE /sse 默认连接 |
否,保留回退 | 第 5.1、8 节 | 当前 LibreChat 支持 Streamable HTTP,新部署优先 /mcp |
--allowed-hosts "*" |
否 | 第 5.2 节 | 企业部署不应关闭 Host 检查 |
18. 证据级别
static_publish_ok: true:Markdown/HTML 已生成;本地结构检查、远端 Markdown/HTML 非空检查和 SHA-256 一致性检查均通过。mock_code_run_ok:不适用;本文没有伪造 Mock 服务来替代真实组件。real_backend_contract_ok:教程配置依据当前 Ollama、LibreChat、Playwright MCP 官方文档做了静态核对,但应在目标团队固定版本后再次执行docker compose config。real_backend_smoke_ok:未执行。本文没有在用户目标内网主机上启动 Ollama、LibreChat 或 Playwright MCP;只有目标环境完成第 6–10 节验收后才能标记为真。