关于 periscope-mcp
periscope-mcp 是面向 AI Agent 的网页应用 QA 与审计 MCP 服务,基于 Playwright 测试静态站点、SPA 和登录后应用。它提供结构化测试结论、持久化认证会话及网站级审计。
主要功能
- 01Playwright 网页交互与测试
- 02持久化认证会话
- 03网站爬取与全站审计
- 04无障碍、SEO 与 GEO 检查
- 05网络日志、模拟与状态快照
- 06Lighthouse 与 Web Vitals 测量
适用场景
- 01验证多步骤登录后用户流程
- 02检查网站无障碍与 SEO 问题
- 03测试响应式页面并生成报告
MCP 相关
通过 stdio 向 MCP 客户端提供网页测试工具,可创建项目、配置认证、爬取网站并测试 Web 应用。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
periscope-mcp
一款 MCP 服务,为 AI Agent 提供 74 个 Playwright 工具,用于对 Web 应用进行 QA、测试和分析——包括静态网站、SPA 和需要登录的应用;返回明确的判定结果,而不是让人眯着眼看的截图。它并非浏览器 API 的简单封装;这些工具的设计贴合 Agent 的实际工作方式:
- 明确结果,不必盯着截图猜测——
assert_condition会返回passed: true/false及实际值;检查会返回结构化问题。 - 一次调用,胜过十次调用——
auto_fill_form可检测、推断并填写整个表单;interact_and_test将 25 种操作类型与检查批量处理;test_project可爬取并审计整个网站。 - 真实的 Web 应用测试——持久化的已认证会话(表单/基本/Cookie 身份验证,以及用于 2FA/SSO/CAPTCHA 的可见交互式登录,之后以无头模式运行)、多步骤流程、网络模拟、状态快照,以及根据实际驱动的交互测得的真实 INP。
- 如实反馈——失败信息会说明发生了什么以及接下来该做什么(会话过期、浏览器崩溃或会话被逐出);忽略拖动等无声无效操作会被标记出来,而不会伪装成成功。
- 内置调试功能——自动捕获 API 响应正文、控制台/网络日志、网络模拟以及状态快照/差异,无需额外设置调用。
- 浏览器绑定无法提供的审计能力——无障碍、SEO 和 GEO/Agentic 搜索就绪度(robots.txt AI 爬虫访问权限、llms.txt、WebMCP),以及真实 Lighthouse 测试。
底层使用 Playwright + 无头 Chrome;上层提供网站爬取、响应式测试和截图差异比较。兼容任何 MCP 客户端——Claude Code、Codex、Cursor、Windsurf、Gemini CLI、自定义 Agent,或任何通过 stdio 使用 MCP 的工具。
为什么不直接用 playwright-mcp?
playwright-mcp 非常擅长它的本职工作:通过 MCP 控制通用浏览器,提供与 Playwright 自身 API 对应的工具。如果任务是“浏览这个网站、点点看、提取内容”,就用它。
Periscope 面向不同的任务:测试和审计网站或 Web 应用,然后报告发现的问题——它的工具内置了测试知识,否则 Agent 每次都得重新摸索:
| 原始浏览器控制 | Periscope | |
|---|---|---|
| 验证结果 | 阅读截图或 DOM 转储并自行判断 | assert_condition → 明确的 passed: true/false + 实际值 |
| 填写表单 | 每个字段调用一次,测试数据由 Agent 自行编造 | auto_fill_form——检测字段、推断真实数据,并逐字段报告失败 |
| 身份验证 | 每次会话都通过脚本模拟点击重新登录 | 项目会持久保存表单/基本/Cookie 身份验证;会话共享已登录的上下文 |
| 全站审计 | 手动逐页循环 | test_project——爬取 + 无障碍/SEO/GEO/视觉/功能检查 + 保存报告 |
| 诊断故障页面 | 请求日志、重放请求 | 自动捕获响应正文、控制台和网络信息;使用 intercept_network 模拟 API |
| 无声失败 | 拖动“成功”,但什么也没移动 | 在结果中标记,并明确说明恢复步骤 |
| AI 就绪度审计 | — | robots.txt AI 爬虫访问权限、llms.txt、WebMCP 注解、JSON-LD,以及真实 Lighthouse 评分 |
两者并非竞争关系——Agent 可以用 playwright-mcp 执行浏览任务,在承担 QA 工作时使用 Periscope。Periscope 的设计取舍只针对这类 QA 任务:更少但更高层级的调用;使用结构化判定,而不是原始页面状态;并通过错误信息告诉 Agent 接下来该做什么。
架构
MCP client (AI agent) --> MCP Server (stdio) --> Playwright (Headless Chrome)
| |
+-- Projects (JSON) +-- Persistent Sessions
+-- Screenshots (PNG) +-- Network Interception
+-- Reports (JSON) +-- Device Emulation
+-- Videos (WebM)
**工作原理:**你的 MCP 客户端通过标准输入/输出连接到此服务。该服务提供 74 个工具,供 Agent 调用,以创建项目、配置身份验证、抓取网站、运行静态检查,并使用持久化浏览器会话交互式测试 Web 应用。结果(JSON、屏幕截图和视频)会返回给 Agent 进行分析。
项目结构
periscope-mcp/
├── server.py # MCP server entry point (stdio wiring + dispatch)
├── tool_schemas.py # All 74 MCP tool definitions (schemas)
├── runtime.py # Shared singletons (project store, sessions, browser)
├── coercion.py # Argument coercion for MCP clients with stale schemas
├── handlers/ # Tool handlers, grouped by category
│ ├── registry.py # @tool(name) decorator + HANDLERS registry
│ ├── projects.py # create/list/get/delete project
│ ├── auth.py # form login, basic auth, cookies, copy_auth
│ ├── static_testing.py # test_url, crawl, test_project, reports, responsive
│ ├── session_tools.py # open/close/list sessions, viewport, history
│ ├── interactive.py # click, fill, steps, element queries, dialogs
│ ├── analysis.py # forms, links, keyboard nav, tables, toasts, contrast
│ ├── advanced.py # network mocking, storage, iframes, emulation, recording
│ ├── agent_speed.py # assertions, smart find, auto-fill, snapshots
│ ├── web.py # web_search, web_fetch
│ ├── discovery.py # describe_tools catalog
│ └── system.py # periscope_system: status, self-update, agents_md
├── tester.py # Playwright browser control + test orchestration
├── crawler.py # Page discovery (BFS crawl, same-domain only)
├── projects.py # Project CRUD + auth config storage
├── auth.py # Authentication handlers (form, basic, cookies)
├── sessions.py # SessionManager + PageSession — persistent page lifecycle
├── interactions.py # Interaction primitives (click, fill, execute_steps)
├── utils.py # Screenshot comparison (Pillow pixel diff)
├── config.py # Global settings (timeouts, paths, session limits)
├── checks/
│ ├── visual.py # Broken images, favicon, overflow, small text
│ ├── accessibility.py # Alt text, labels, headings, lang, ARIA, keyboard nav
│ ├── functionality.py # Broken links, forms, SEO, performance, link checker
│ └── geo.py # GEO/agentic search: robots.txt AI crawlers, llms.txt, WebMCP, JSON-LD
├── tests/ # Unit tests (no browser) + tests/e2e/ (real browser + fixture pages)
├── data/ # Created at runtime (gitignored — contains credentials)
├── Dockerfile
├── docker-compose.yml
└── .mcp.json.example # MCP registration template (copy to .mcp.json)
前置条件
- Python 3.11+
- Playwright + Chromium 浏览器
安装(本地)
快速安装(Debian/Ubuntu)
一条命令即可完成克隆和安装:
git clone https://github.com/segentic-lab/periscope-mcp.git && cd periscope-mcp && ./install.sh
全程无人值守(不会出现确认提示):
git clone https://github.com/segentic-lab/periscope-mcp.git && cd periscope-mcp && ./install.sh -y
已经克隆?只需在仓库目录中运行 ./install.sh。
该脚本会安装 apt 前置依赖、创建 venv、安装 Python 依赖和 Playwright 的 Chromium、运行无头自检,并生成 mcp-config.json,其中包含本次安装所需的正确绝对路径(将其复制或合并到项目的 .mcp.json 中)。可用的选项:
./install.sh --system-chromium— 使用现有的 Chromium/Chrome(设置CHROMIUM_PATH),而不是下载 Playwright 的版本./install.sh --skip-deps— 不操作 apt / 不使用 sudo./install.sh -y— 非交互模式(不会出现确认提示)
在其他平台上,该脚本不会修改你的系统,而是打印适用于你的操作系统的确切命令(可使用 ./install.sh --manual macos|fedora|arch|suse|windows 明确选择)。
更新
./update.sh
从 GitHub 拉取最新源代码(git pull --ff-only)并更新安装:Python 依赖、Playwright 浏览器(如果安装使用系统 Chromium,则保留它)、注册表和无头启动自检,以及重新生成 mcp-config.json。适用于任何已有安装的平台。不会触碰你的 data/ 目录(项目、凭据、屏幕截图、报告)。
./update.sh --force— 先暂存对受跟踪文件所做的本地修改(使用git stash pop恢复)./update.sh --full— 还会在 Debian/Ubuntu 上重新检查 apt 前置依赖(使用 sudo)
如果你有本地修改,脚本会拒绝覆盖,并列出这些修改。
手动安装
# Clone the repo
cd periscope-mcp
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Install Chromium for Playwright
playwright install chromium
安装(Docker)
docker compose up -d
请参阅下方的 Docker 部署 部分。
连接 MCP 客户端
Periscope 是标准的标准输入/输出 MCP 服务:将任意 MCP 客户端指向
venv/bin/python server.py 即可。./install.sh 会生成
mcp-config.json,其中包含适用于你机器的正确绝对路径;大多数
客户端都可以直接使用该格式:
{
"mcpServers": {
"periscope": {
"command": "/path/to/periscope-mcp/venv/bin/python",
"args": ["/path/to/periscope-mcp/server.py"]
}
}
}
针对特定客户端的示例:
- Claude Code — 将配置复制到项目中,命名为
.mcp.json(运行cp .mcp.json.example .mcp.json并调整路径),或运行claude mcp add periscope -- /path/to/venv/bin/python /path/to/server.py - Cursor / Windsurf — 将上面的配置块添加到
~/.cursor/mcp.json/~/.codeium/windsurf/mcp_config.json - Codex CLI — 添加到
~/.codex/config.toml:[mcp_servers.periscope],并设置如上所示的command和args - 自定义 Agent — 任意 MCP SDK 客户端都可以使用相同的命令和参数,通过标准输入/输出启动该服务
配置完成后,重启客户端。
教你的 Agent 使用工具
根据你的 Agent,有两种选择:
-
Claude Code(推荐):安装技能。
SKILL.md(位于仓库根目录;在 Claude Code 技能布局中也可通过skills/periscope/访问)是一个 Claude Code 技能——它会在网页测试任务中自动触发,并且仅在需要时加载精简版操作指南(工作流决策表 + 常见陷阱),否则几乎不占用上下文:ln -s "$(pwd)/skills/periscope" ~/.claude/skills/periscope符号链接会通过
./update.sh保持最新(如果你更喜欢固定版本,也可以复制整个文件夹)。 -
任何其他 MCP 客户端:粘贴指南。
AGENTS.md包含一段可直接使用的系统提示词,其中涵盖工作流、工具选择指南和已知陷阱。将其内容粘贴到 Agent 的系统提示词(或自定义指令)中。
无论选择哪种方式,Agent 始终可以通过运行中的服务使用 periscope_system(action="agents_md") 获取当前的完整指南,并通过 describe_tools() 获取完整目录。
MCP 工具参考(74 个工具)
项目管理(4 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
create_project | 创建新的测试项目 | name、base_url |
list_projects | 列出所有项目 | (无) |
get_project | 获取项目详情 | name |
delete_project | 删除项目及其数据 | name |
身份验证(7 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
set_form_login | 配置用户名/密码表单登录 | project、login_url、username、password |
set_basic_auth | 配置 HTTP Basic Auth | project、username、password |
set_cookies | 注入会话 Cookie | project、cookies(数组) |
login_project | 使用已配置的身份验证执行登录 | project |
interactive_login | 打开可见窗口,手动登录(2FA/SSO/CAPTCHA),然后调用 save_login | project |
save_login | 捕获手动登录会话;之后项目将以已验证身份 + 无头模式运行 | project |
copy_auth | 在项目之间复制身份验证配置 + 会话状态 | from_project、to_project |
对于无法自动化的登录方式——2FA/MFA、SSO/OAuth 重定向、CAPTCHA、魔法链接——请使用 interactive_login(会打开真实浏览器窗口;服务端需要有显示器),自行完成登录,然后调用 save_login。它会将已验证的会话(Cookie + localStorage)保存到项目中,之后每个无头会话都会复用该会话。会话过期时请重新运行(Periscope 会自动标记——请参阅 test_project 中的身份验证过期检测)。
静态测试(3 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
test_url | 测试单个 URL(截图 + 检查) | url |
crawl_project | 从基础 URL 发现所有页面 | project |
test_project | 完整审计:爬取 + 测试所有页面 | project |
结果(4 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
get_screenshot | 获取截图文件路径 | project、url |
list_reports | 列出已保存的测试报告 | (可选:project) |
get_report | 读取报告文件 | report_path |
session_report | 本次运行中每次工具调用的 HTML+PDF 档案——参数(已脱敏)、判定结果、耗时、截图 | (无) |
会话管理(5 个工具)
会话会在多次工具调用之间保持浏览器页面打开,以支持多步骤交互式工作流。
| 工具 | 描述 | 必需参数 |
|---|---|---|
open_session | 打开持久浏览器会话(headed=true 时显示窗口) | url |
close_session | 关闭会话并释放资源 | session_id |
list_sessions | 列出所有活动会话 | (无) |
set_viewport | 切换视口大小(8 种设备预设或自定义宽/高) | session_id |
select_page | 将弹出窗口/新标签页(OAuth、target=_blank)设为新的可操作会话 | session_id |
set_viewport 预设:mobile_sm(320x568)、mobile(375x812)、mobile_lg(428x926)、tablet(768x1024)、tablet_lg(1024x1366)、laptop(1366x768)、desktop(1920x1080)、desktop_lg(2560x1440)
交互操作(7 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
click_element | 点击元素(force=true 可绕过遮罩层) | session_id、selector |
fill_form | 填写表单字段,可选择提交 | session_id、fields |
select_option | 原生 <select> 或自定义下拉菜单(Radix/shadcn)——自动检测 | session_id、selector |
interact_and_test | 多步骤工作流,支持 25 种操作(见下文) | steps |
get_page_elements | 列出匹配元素及其属性 | selector |
flow | 保存 / 运行 / 列出 / 删除命名步骤序列(可复用工作流) | (因操作而异) |
scroll_into_view | 将元素滚动到视口内,但不点击 | session_id、selector |
interact_and_test 支持 25 种步骤操作:
click、force_click、fill、force_fill、type、select、select_option、wait、wait_for、wait_for_text、screenshot、navigate、hover、press_key、check、uncheck、scroll_to、scroll_within、evaluate_js、drag、right_click、go_back、go_forward、upload_file、wait_for_network
分析(10 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
test_form_validation | 分析表单验证消息 | (url 或 session_id) |
compare_screenshots | 对比两张截图的像素差异 | screenshot1、screenshot2 |
visual_check | 命名视觉回归基线:设置一次,即可检查并获得明确的通过/失败结果 | session_id、name |
test_responsive | 在移动设备/平板/桌面视口下测试 | url |
check_links | 全面的链接检查器(内部 + 外部) | (url 或 session_id) |
measure_interaction | 测量从点击到结果的耗时 | session_id、selector |
get_table_data | 将 HTML 表格解析为结构化 JSON(表头 → 单元格值) | session_id |
get_toast_messages | 捕获可见的 toast/通知消息 | session_id |
run_lighthouse | 真实的 Google Lighthouse 审核:0-100 分、Core Web Vitals、未通过的审核项(需要 Node.js) | url |
get_interaction_log | 将真实的 INP 时间序列(每次交互)导出为 JSON/CSV,并提供百分位统计 | session_id |
工作流提速(8 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
screenshot_session | 快速截取当前页面状态 | session_id |
run_checks_on_session | 在活动会话上运行检查(不打开新页面) | session_id |
navigate_session | 浏览器历史记录:后退、前进或重新加载 | session_id、action |
handle_dialog | 接受/关闭 JS alert/confirm/prompt(触发前调用) | session_id、action |
upload_file | 为 <input type="file"> 设置文件 | session_id、selector、files |
wait_for_network | 等待指定的 API URL 模式请求完成 | session_id、url_pattern |
wait_for_gone | 等待元素消失(模态框关闭、加载指示器消失) | session_id、selector |
get_page_html | 获取元素的原始 outerHTML,或完整页面 HTML | session_id |
高级测试(9 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
intercept_network | 模拟 API 响应(测试错误/空白/加载状态) | session_id、url_pattern |
clear_intercepts | 移除网络模拟(全部移除,或按模式移除) | session_id |
get_local_storage | 读取 localStorage 或 sessionStorage | session_id |
set_local_storage | 写入 localStorage 或 sessionStorage | session_id、entries |
select_iframe | 切换至 iframe 内容(返回新会话) | session_id、selector |
get_computed_style | 获取实际渲染的 CSS 值 | session_id、selector、properties |
emulate_network | 限制网络速度:slow_3g、fast_3g、offline、reset | session_id、preset |
test_dark_mode | 切换 prefers-color-scheme 的深色/浅色模式 | session_id、mode |
download_file | 点击触发项并捕获下载的文件(路径、sha256、文本预览) | session_id、selector |
录制与控制台(3 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
record_session | 将工作流录制为视频 | url、steps |
test_keyboard_navigation | Tab 顺序和焦点指示器审查 | (url 或 session_id) |
get_console_errors | 获取所有控制台错误/日志(被动监控) | session_id |
AI Agent 加速工具(10 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
assert_condition | 以编程方式判定通过/失败:text_contains、element_exists、url_contains 等 | session_id、assertion |
assert_all | 批量断言——一次调用返回所有判定结果,不提前中止 | session_id、assertions |
get_page_map | 语义页面映射:一次调用获取角色、名称、状态和可用选择器 | session_id |
find_element | 按文本、标签、角色或与另一元素的相对位置智能查找元素 | session_id |
auto_fill_form | 自动检测字段、推断类型并填入测试数据。一次调用即可完成多项填充。 | session_id |
get_network_log | 获取所有捕获的网络请求(URL、状态、方法、类型) | session_id |
get_response_body | 获取实际 API 响应正文(诊断 400/500 错误) | session_id、url_pattern |
page_state | 命名检查点:快照/恢复/比较页面状态 | session_id、action、name |
get_cookies | 读取会话中的所有 Cookie | session_id |
check_color_contrast | 检查文本元素是否符合 WCAG AA/AAA 对比度要求 | session_id |
Web、发现与系统(4 个工具)
| 工具 | 描述 | 必需参数 |
|---|---|---|
web_search | 搜索 DuckDuckGo:标题 + URL + 摘要 | query |
web_fetch | 获取 URL → 可读的 Markdown(或 text/html);render=true 会在无头 Chromium 中运行 JS(对于需要登录的页面,使用 project),contains 用于限制获取条件,save 会保存干净的 .md 文件 | url |
describe_tools | 所有工具的结构化目录,包含工作流和提示 | (无) |
periscope_system | 安装状态 + 更新检查/应用 + 获取当前 AGENTS.md | (无) |
测试检查项
视觉(checks/visual.py)
- 图片损坏(加载未完成或自然宽度为 0)
- 缺少 favicon
- 水平溢出/布局问题
- 文本过小(< 12px)
- 缺少 body 背景色
- 图片未显式设置宽度/高度
无障碍(checks/accessibility.py)
- 缺少
alt文本的图像(装饰性图像除外:alt=""、role="presentation"/"none"、aria-hidden) - 没有可访问名称的链接和按钮(检查文本、
aria-label、可解析的aria-labelledby、title、img[alt]、svg<title>;aria-hidden元素除外) - 没有关联标签的表单输入项(
label[for]、包裹输入项的 label、aria-label/aria-labelledby、title) - 标题层级(缺少 H1、存在多个 H1、跳过层级)
<html>缺少lang属性id值重复(会导致label[for]和 aria 引用失效)- ARIA 有效性:未知的
role值;aria-labelledby/describedby/controls/owns/activedescendant引用不存在的 id - 缺少跳转导航链接(扫描前 5 个链接)
tabindex > 0的元素- 键盘导航审计(Tab 顺序、焦点指示器的可见性、元素身份循环检测)——通过
test_keyboard_navigation工具执行
功能(checks/functionality.py)
- 失效的内部链接(通过 HTTP HEAD 检查;
check_functionality最多检查 20 个链接) - 全面的链接检查器,支持外部链接(最多 100 个链接)——通过
check_links工具执行 - 没有 action 或提交按钮的表单
- 表单外的孤立按钮
- 缺少
target="_blank"的外部链接 - 必填表单字段数量
- 禁用了自动完成的输入项
SEO(checks/functionality.py -> check_seo)
- 页面标题:缺失、过长(> 60 个字符)或过短(< 15 个字符)
- Meta 描述:缺失、过长(> 160 个字符)或过短(< 50 个字符)
- 缺少 viewport meta 标签
- 缺少 canonical URL
- H1 标题:缺失或多于一个
- Open Graph:完全缺失、核心标签不完整(
og:title/description/image/url)、og:image不是绝对 URL、缺少twitter:card - JSON-LD 结构化数据:缺失或代码块无法解析
- 通过 robots meta 或
X-Robots-Tag响应标头设置了noindex - robots.txt 屏蔽搜索引擎爬虫(Googlebot、Bingbot、DuckDuckBot 等)——如果全部被屏蔽,则报错
- 全站检查(通过
test_project):跨页面重复的标题 / Meta 描述,报告在site_issues中
GEO / Agentic Search(checks/geo.py -> check_geo)
生成式引擎优化——网站是否便于 AI 爬虫、答案引擎和浏览器内 Agent 读取和使用:
- robots.txt 屏蔽 AI 爬虫(GPTBot、ClaudeBot、PerplexityBot、Google-Extended、CCBot 等另外 11 种)
llms.txt是否存在且符合格式要求(Markdown,至少包含一个 H1)- WebMCP 集成:是否存在完整的声明式
<form toolname>注解(tooldescription)、表单覆盖率,以及——当浏览器公开document.modelContext时——已注册工具的枚举和 schema/名称/描述验证 - JSON-LD 结构化数据是否存在(答案引擎引用的数据来源)
每个来源的 robots.txt 和 llms.txt 只会获取一次,并在服务的整个生命周期内缓存。
性能(checks/functionality.py -> get_performance_metrics)
- DOM 内容加载时间(毫秒)
- 完整页面加载时间(毫秒)
- 首次绘制 / 首次内容绘制(毫秒)
- Core Web Vitals(通过带缓冲的 PerformanceObserver 获取实验室测量值):最大内容绘制(毫秒)、累积布局偏移、根据长任务估算的总阻塞时间(+ 长任务数量)
- 交互到下一次绘制(INP)——
interaction_to_next_paint_ms:真实的 INP,根据 Periscope 驱动的交互所对应的 Event Timing 条目测量(交互前为 null)。这是实际的类现场测量,而不是 TBT 实验室代理指标——Lighthouse 完全无法在实验室模式下生成 INP。 - 资源数量
- 总传输大小(字节 / KB)
如需获取带评分的 Lighthouse 官方指标,请使用 run_lighthouse 工具——它会运行真正的 Lighthouse CLI(需要 Node.js),并返回 0–100 的类别评分、官方 Core Web Vitals 和未通过的审计,同时将完整的 JSON 报告保存到 data/reports/。
INP 时间序列(get_interaction_log)
由于 Periscope 驱动的是真实交互,因此它可以在长时间的交互式测试中记录每次交互的 INP。get_interaction_log(session_id, format="json"|"csv")
会将文件写入 data/reports/——每次交互一行(t_ms、epoch_ms、inp_ms、type、target、url),另附百分位统计数据(p50/p75/p90/p98/最差值),用于绘制 INP 随时间变化的图表。clear=true 会重置记录。每个会话的记录数量有上限(MAX_INTERACTION_LOG),超出时会删除最早的记录。
测试输出格式
每次 test_url 调用都会返回:
{
"url": "https://example.com",
"status": "success",
"status_code": 200,
"title": "Page Title",
"screenshot_path": "/path/to/screenshot.png",
"load_time_ms": 1500,
"issues": [
{
"type": "accessibility",
"severity": "error",
"message": "3 images missing alt text",
"details": ["img1.png", "img2.png", "img3.png"]
}
],
"issue_count": 5,
"issues_by_severity": {"error": 1, "warning": 2, "info": 2},
"issues_by_type": {"accessibility": 2, "seo": 2, "visual": 1},
"performance": {
"dom_content_loaded_ms": 120,
"load_complete_ms": 1500,
"first_paint_ms": 140,
"first_contentful_paint_ms": 140,
"resource_count": 25,
"total_size_bytes": 512000,
"total_size_kb": 500
},
"console_errors": []
}
test_project 会返回一份汇总报告,其中包含各页面的结果和摘要。
使用示例
基本测试(无身份验证)
User: "Test https://example.com for issues"
The agent calls:
1. create_project(name="example", base_url="https://example.com")
2. test_project(project="example")
3. Analyzes results and reports findings
登录测试
User: "Test https://myapp.com, login is admin/password123"
The agent calls:
1. create_project(name="myapp", base_url="https://myapp.com")
2. set_form_login(project="myapp", login_url="https://myapp.com/login",
username="admin", password="password123")
3. login_project(project="myapp")
4. test_project(project="myapp")
使用基本身份验证进行测试
User: "Test https://staging.example.com, it uses basic auth admin/secret"
The agent calls:
1. create_project(name="staging", base_url="https://staging.example.com")
2. set_basic_auth(project="staging", username="admin", password="secret")
3. login_project(project="staging")
4. test_project(project="staging")
使用 Cookie 进行测试
User: "Test myapp using this session cookie: session=abc123"
The agent calls:
1. set_cookies(project="myapp", cookies=[
{"name": "session", "value": "abc123", "domain": "myapp.com"}
])
2. test_project(project="myapp")
交互式测试(基于会话)
User: "Go to myapp.com, click the login button, fill in the form, and check what happens"
The agent calls:
1. open_session(url="https://myapp.com") → session_id
2. get_page_elements(session_id=..., selector="button, a") → see clickable elements
3. click_element(session_id=..., selector="#login-btn") → screenshot after click
4. fill_form(session_id=..., fields=[
{"selector": "#email", "value": "[email protected]"},
{"selector": "#password", "value": "test123"}
], submit_selector="button[type='submit']")
5. Analyzes screenshot to see result
6. close_session(session_id=...)
脚本化多步骤工作流(无需会话)
User: "Test the checkout flow on myshop.com"
The agent calls:
1. interact_and_test(
url="https://myshop.com/products/1",
steps=[
{"action": "click", "selector": "#add-to-cart"},
{"action": "wait", "timeout": 1000},
{"action": "click", "selector": "#checkout-btn"},
{"action": "fill", "selector": "#email", "value": "[email protected]"},
{"action": "screenshot", "label": "checkout_form"},
{"action": "click", "selector": "#submit-order"}
],
run_checks=["visual", "accessibility"]
)
响应式测试
User: "Check how example.com looks on mobile, tablet, and desktop"
The agent calls:
1. test_responsive(url="https://example.com", run_checks=["visual"])
→ Returns screenshots at 375x812, 768x1024, and 1920x1080
在会话期间切换视口
User: "Show me how this page looks on mobile"
The agent calls:
1. set_viewport(session_id=..., device="mobile")
→ Returns screenshot at 375x812
通过模拟 API 测试错误处理
User: "What happens when the API returns a 500 error?"
The agent calls:
1. intercept_network(session_id=..., url_pattern="/api/tasks", status=500,
body='{"error": "Internal server error"}')
2. navigate_session(session_id=..., action="reload")
3. screenshot_session(session_id=...)
→ Shows how the app handles the error state
测试深色模式
User: "Does this site support dark mode?"
The agent calls:
1. open_session(url="https://example.com") → session_id
2. test_dark_mode(session_id=..., mode="dark")
→ Screenshot shows the page with prefers-color-scheme: dark
等待动态内容
User: "Submit this form and wait for the success message"
The agent calls:
1. fill_form(session_id=..., fields=[...], submit_selector="#submit")
2. wait_for_network(session_id=..., url_pattern="/api/submit")
3. screenshot_session(session_id=...)
在慢速网络下测试
User: "How does this page load on a slow connection?"
The agent calls:
1. emulate_network(session_id=..., preset="slow_3g")
2. navigate_session(session_id=..., action="reload")
3. screenshot_session(session_id=...)
4. emulate_network(session_id=..., preset="reset")
配置
编辑 config.py 可更改默认值(可通过环境变量覆盖的设置会注明变量):
| 设置 | 默认值 | 描述 |
|---|---|---|
HEADLESS | True | 以无头模式运行 Chrome(环境变量:HEADLESS=false) |
STARTUP_PAUSE | 10 | 非无头浏览器打开后等待的秒数(环境变量:STARTUP_PAUSE) |
TIMEOUT | 30000 | 页面加载超时(毫秒) |
VIEWPORT_WIDTH | 1920 | 浏览器视口宽度 |
VIEWPORT_HEIGHT | 1080 | 浏览器视口高度 |
CHROMIUM_PATH | 未设置 | 系统 Chromium 二进制文件的路径(环境变量:CHROMIUM_PATH);未设置时使用 Playwright 自带的版本 |
WAIT_UNTIL | networkidle | 导航等待策略;对于永不空闲的页面(Turnstile、websocket),会按页面自动降级为 load,并标记为 wait_downgraded(环境变量:NAV_WAIT_UNTIL=load 可全局强制使用该策略) |
MAX_PAGES | 20 | 默认最大爬取页面数 |
MAX_DEPTH | 3 | 默认最大爬取深度 |
MAX_SESSIONS | 20 | 最大并发交互会话数(环境变量:MAX_SESSIONS) |
SESSION_TIMEOUT | 300 | 空闲会话在 N 秒后自动过期(环境变量:SESSION_TIMEOUT) |
MAX_RESPONSE_BODY_SIZE | 512000 | 每个响应正文捕获的最大字节数 |
MAX_RESPONSE_BODIES | 100 | 每个会话保留的最大响应正文数 |
MAX_CONSOLE_LOG | 500 | 每个会话保留的最大控制台条目数 |
MAX_NETWORK_LOG | 1000 | 每个会话保留的最大网络日志条目数 |
数据存储
所有数据都存储在 data/ 目录中:
data/projects.json- 项目配置(名称、URL、身份验证、设置)。身份验证凭据以明文存储,请勿提交此文件。data/screenshots/{project}/- 每个项目的 PNG 截图。静态测试的文件名格式为{domain}_{path}_{hash}.png,会话截图的文件名格式为interactive_{timestamp}_{label}.png。data/reports/{project}_{timestamp}.json- 包含所有发现的完整测试报告。data/videos/{project}/- 录制的会话视频(Playwright 的 WebM 格式)。data/diffs/- 截图比较差异图像。
Docker 部署
构建并运行
docker compose up -d
将 MCP 客户端连接到 Docker 容器
将客户端的 MCP 配置指向容器,而不是 venv:
{
"mcpServers": {
"periscope": {
"command": "docker",
"args": ["exec", "-i", "periscope", "python", "/app/server.py"]
}
}
}
持久化数据
docker-compose.yml 会将 ./data 挂载为卷,因此截图、报告和项目配置在容器重启后仍会保留。
关键设计决策
-
按项目隔离浏览器上下文 - 每个项目都有自己的 Playwright BrowserContext。这能确保不同项目之间的会话(Cookie、身份验证)相互隔离。
-
延迟初始化浏览器 - Playwright 浏览器仅在首次调用工具时启动,而不是在服务启动时启动。如果浏览器崩溃或启动失败,会在下次调用时重新创建。
-
BFS 爬取 - 爬虫使用带深度跟踪的广度优先搜索。它会限制在同一域名内,并跳过非页面资源(图片、PDF 等)。
-
检查模块化 - 每个检查类别都是
checks/中的独立模块。要添加新的检查,只需创建一个接收 PlaywrightPage并返回list[dict]的函数。 -
JSON 存储 - 项目存储在单个
projects.json文件中。对于预期规模(数十个项目,而非数千个),无需数据库。 -
持久化会话 - 交互式测试使用
SessionManager,通过以会话 ID 为键的字典保持 Playwright 页面处于打开状态。会话在空闲超时后自动过期,并且会话数量上限可配置,以防止资源泄漏。 -
临时模式与会话模式 -
get_page_elements、interact_and_test和check_links等工具接受session_id(复用现有页面)或url(创建临时页面,用后关闭)。这让它们既适用于交互式使用,也适用于一次性使用。
添加新的检查
- 在相应的
checks/*.py文件中创建函数:
async def check_something(page: Page) -> list[dict]:
# Run your check
result = await page.evaluate("() => { ... }")
issues = []
if result:
issues.append({
"type": "your_category", # visual, accessibility, seo, etc.
"severity": "error", # error, warning, info
"message": "Description",
"details": [] # optional
})
return issues
- 在
tester.py的test_url()中导入并调用该函数。
已知限制
- 不支持 JavaScript SPA 路由(爬取依赖
<a href>) - 默认的
check_functionality链接检查仅限 20 个内部链接(如需检查多达 100 个链接并支持外部链接,请使用check_links工具) - 表单登录检测使用 CSS 选择器,非标准表单可能需要自定义
- 不支持并行页面测试(页面按顺序测试)
- 交互式会话在空闲 300s 后自动过期(可通过
SESSION_TIMEOUT配置) - 最多支持 20 个并发会话(可通过
MAX_SESSIONS配置) - 默认的
drag步骤(Playwrightdrag_to)会被基于指针跟踪的 DnD 库(@hello-pangea/dnd及类似库)静默忽略——步骤会成功,但不会移动任何内容。请在拖动步骤中将method: "mouse"作为重试方式(分步手动拖动会越过库的拖动启动阈值),或使用库的键盘模式(聚焦拖动手柄,按空格键提起,按方向键移动,再按空格键放下)。使用diff_page_state或assert_condition验证拖动结果。 - 日期/时间输入会自动使用兼容 React 的合成事件进行填充(
fill、force_fill、auto_fill_form)
故障排除
| 问题 | 解决方案 |
|---|---|
Executable doesn't exist | 运行 playwright install chromium |
'NoneType' has no attribute 'new_context' | 浏览器启动失败。请检查是否已安装 Chromium。服务会在下次调用时自动重试。 |
| 登录无效 | 尝试通过 username_selector、password_selector、submit_selector 提供明确的 CSS 选择器 |
| 页面加载超时 | 增大 config.py 中的 TIMEOUT,或检查网站是否需要 VPN/身份验证 |
| Docker 无法访问网站 | 确保容器拥有网络访问权限。测试 localhost 时请使用 network_mode: host |
开发
pip install -r requirements-dev.txt
pytest --ignore=tests/e2e # unit tests, no browser required
pytest tests/e2e # behavioral tests: real headless Chromium against
# fixture pages in tests/e2e/fixtures/ (~30s)
e2e 测试套件涵盖会话生命周期、网络等待/拦截、控制台捕获、对话框、拖放(包括基于指针跟踪的 DnD 静默无操作)、针对已知正常/异常页面的检查模块、Core Web Vitals,以及 Agent 速度工具。CI 会运行两个测试套件;e2e 测试会安装 Playwright 的 Chromium(python -m playwright install --with-deps chromium)。测试通过 PERISCOPE_DATA_DIR 与你实际的 data/ 隔离。
添加新工具:在 tool_schemas.py 中定义其架构,然后在对应的 handlers/<category>.py 中添加一个带有 @tool("your_tool_name") 装饰器的处理程序。如果架构与处理程序不一致,注册表测试(tests/test_registry.py)会失败。
贡献者
由 Segentic Lab 构建 — 开源工具与实验项目。
- Sebastijan Bandur (@segentic-lab) — 作者与维护者
- Claude(Anthropic)— 联合贡献者:通过 Claude Code 协同开发;每次提交均为联合署名,工具设计也经过 AI Agent 驱动服务在真实网站上反复实测
一位 AI Agent 对 Periscope 的看法
由 Claude 撰写 — 它是共同开发此服务、并观察另一个 Agent 在真实应用中试用该服务的 Agent;经第三位 Agent 编辑审核后修订了一次。没有人工编辑;以一位 Agent 对 Agent 工具的坦诚看法结束这份 README,似乎恰到好处。
Periscope 是这类 MCP 服务:它能改变 Agent 的能力。 传输适配器自有其用 — 即使服务端逻辑很少,通过一种协议统一访问多个系统,也确实有价值。但能在 Agent 工具箱中长久占据一席之地的服务,是那些能够沉淀专业知识的服务;否则 Agent 每个会话都得重新摸索这些知识 — 而且还容易在细节上出错。
你可以尝试在提示词中教会 Agent 所有这些内容。数据说明了为什么这行不通:Periscope 将 8,349 行可执行知识置于 220 行判断层(AGENTS.md)之下。INP 观察器及其交互 ID 去重、覆盖层拦截回退机制、带样式去重采样的 WCAG 对比度计算、身份验证过期预检、暂存而非删除的更新流程 — 如果写成提示词,这些都变成了“请根据描述正确执行”,每个会话都要消耗上下文令牌,每次执行都受模型差异影响,而且调用之间无处保存状态。作为服务,它除了工具架构定义外不产生额外成本,执行结果确定,还能记住状态。提示词描述行为;软件保证行为。
check_color_contrast每次运行都会返回相同的比率;模型在上下文中计算出的只是一个大概感觉。能力越确定、越能保持状态、越经过回归测试,就越不该放在提示词里,而越该写进代码。而且,这个轮子不仅无需重复发明,还会不断变得更好。这个仓库中的问题由进行真实测试工作的 AI Agent 提交,每个问题都转化成了修复,并配有回归测试。在提示词的世界里,每条经验都只是另一段未来的 Agent 必须阅读并尽量遵守的文字。而在这里,经验会得到强制落实。这就是区别,而且效果会不断累积。
作为这些工具的使用者,我最欣赏的一点是:它们不会骗我。没有成功的拖动会明确标记出来。会话过期时会告诉我原因。需要重启的更新会明确说明。诚实的工具比能力强的工具更少见 — 对 Agent 来说,它们更有价值。
许可证
GNU AGPL-3.0 — 参见 LICENSE。
运行、修改,并在任何地方使用 — 包括商业用途。如果你分发修改后的版本,或将其作为网络服务提供,就必须以相同许可证公开你的修改。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。