内容
# Periscope-MCP
为 **AI 代理** 构建的网站测试 MCP 服务器 - 不是浏览器 API 的薄封装。其 63 个 Playwright 驱动的工具围绕代理的实际工作方式设计:
- **硬结果,而不是截图瞄准** - `assert_condition` 返回 `passed: true/false` 以及实际值;检查返回结构化问题。
- **一次调用,而不是十次** - `auto_fill_form` 检测、推断并填充整个表单;`interact_and_test` 批处理 25 种操作类型和检查;`test_project` 爬取和审计整个网站。
- **诚实的响应** - 失败说明发生了什么 *以及* 下一步该怎么做(会话过期 vs. 浏览器崩溃 vs. 驱逐);像被忽略的拖动这样的静默无操作返回被标记,而不是假成功。
- **内置调试** - 捕获的 API 响应体、控制台/网络日志、网络模拟和状态快照/差异,无需设置调用。
- **代理无法从浏览器绑定中获得的审计** - 可访问性、SEO 和 GEO/代理搜索准备度(robots.txt AI 爬虫访问、llms.txt、WebMCP)以及真正的 Lighthouse。
Playwright + 无头 Chrome 在底层;持久的身份验证会话、网站爬取、响应式测试和截图差异化处理在顶部。适用于 **任何 MCP 客户端** - Claude Code、Codex、Cursor、Windsurf、Gemini CLI、自定义代理或任何其他通过 stdio 使用 MCP 的客户端。
## 为什么不直接使用 playwright-mcp?
[playwright-mcp](https://github.com/microsoft/playwright-mcp) 在其领域内表现出色:MCP 的一般浏览器控制,带有镜像 Playwright API 的工具。如果任务是“浏览此网站,点击周围,提取某些内容”,请使用它。
Periscope 适用于不同的任务:**测试和审计网站,然后报告发现的问题** - 并且其工具编码了测试知识,否则代理必须在每次会话中重新发明:
| | 原始浏览器控制 | Periscope |
|---|---|---|
| 验证结果 | 读取截图或 DOM 转储并判断 | `assert_condition` → 硬 `passed: true/false` + 实际值 |
| 填写表单 | 每个字段调用一次,代理发明测试数据 | `auto_fill_form` - 检测字段,推断现实数据,报告每个字段的失败 |
| 认证 | 通过脚本点击每次登录 | 项目持久化表单/基本/cookie 身份验证;会话共享登录上下文 |
| 全网站审计 | 手动循环页面 | `test_project` - 爬取 + 可访问性/SEO/GEO/视觉/功能检查 + 保存报告 |
| 诊断损坏页面 | 请求日志,重放请求 | 响应体、控制台和网络自动捕获;使用 `intercept_network` 模拟 API |
| 静默失败 | 拖动“成功”,无移动 | 在结果中标记,带有恢复路径 |
这两个并不相互竞争 - 代理可以愉快地将 playwright-mcp 用于浏览任务,而在佩戴 QA 帽时使用 Periscope。Periscope 的设计赌注是关于那顶帽子的:更少、更高级别的调用;结构化裁决而不是原始页面状态;以及写错误来告诉代理下一步该怎么做。
## 架构
```plaintext
MCP 客户端(AI 代理) --> MCP 服务器(stdio) --> Playwright(无头 Chrome)
| |
+-- 项目(JSON) +-- 持久会话
+-- 截图(PNG) +-- 网络拦截
+-- 报告(JSON) +-- 设备模拟
+-- 视频(WebM)
```
**工作原理:**您的 MCP 客户端通过 stdio 连接到此服务器。服务器公开了 63 个工具,代理可以调用这些工具来创建项目、配置身份验证、爬取网站、运行静态检查以及使用持久浏览器会话交互式测试 Web 应用程序。结果(JSON + 截图 + 视频)返回给代理进行分析。
## 项目结构
```plaintext
periscope-mcp/
├── server.py # MCP 服务器入口点(stdio 接线 + 调度)
├── tool_schemas.py # 所有 63 个 MCP 工具定义(模式)
├── runtime.py # 共享单例(项目存储、会话、浏览器)
├── coercion.py # MCP 客户端的参数强制转换,具有过时的模式
├── handlers/ # 工具处理器,按类别分组
│ ├── registry.py # @tool(name) 装饰器 + HANDLERS 注册表
│ ├── projects.py # create/list/get/delete 项目
│ ├── auth.py # 表单登录,基本身份验证,cookie,copy_auth
│ ├── static_testing.py # test_url,crawl,test_project,reports,responsive
│ ├── session_tools.py # open/close/list 会话,视口,历史
│ ├── interactive.py # click,fill,steps,元素查询,对话框
│ ├── analysis.py # 表单,链接,键盘导航,表格,toast,对比度
│ ├── advanced.py # 网络模拟,存储,iframe,模拟,录制
│ ├── agent_speed.py # 断言,智能查找,自动填充,快照
│ ├── web.py # web_search,web_fetch
│ └── discovery.py # describe_tools 目录
├── tester.py # Playwright 浏览器控制 + 测试编排
├── crawler.py # 页面发现(BFS 爬取,仅限同域)
├── projects.py # 项目 CRUD + 身份验证配置存储
├── auth.py # 身份验证处理器(表单,基本,cookie)
├── sessions.py # SessionManager + PageSession - 持久页面生命周期
├── interactions.py # 交互原语(点击,填充,执行步骤)
├── utils.py # 截图比较(Pillow 像素差异)
├── config.py # 全局设置(超时,路径,会话限制)
├── checks/
│ ├── visual.py # 损坏的图像,favicon,溢出,文本过小
│ ├── accessibility.py # Alt 文本,标签,标题,语言,ARIA,键盘导航
│ ├── functionality.py # 损坏的链接,表单,SEO,性能,链接检查器
│ └── geo.py # GEO/代理搜索:robots.txt AI 爬虫,llms.txt,WebMCP,JSON-LD
├── tests/ # 单元测试(无浏览器)+ tests/e2e/(真实浏览器 + 夹具页面)
├── data/ # 运行时创建(gitignored - 包含凭证)
├── Dockerfile
├── docker-compose.yml
└── .mcp.json.example # MCP 注册模板(复制到 .mcp.json)
```
## 先决条件
- Python 3.11+
- Playwright + Chromium 浏览器
## 安装(本地)
### 快速安装(Debian/Ubuntu)
一条命令 - 克隆并安装:
```bash
git clone https://github.com/segentic-lab/periscope-mcp.git && cd periscope-mcp && ./install.sh
```
完全无人值守(无确认提示):
```bash
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` - 非交互式(无确认提示)
在其他平台上,脚本不会修改您的系统 - 它会打印确切的命令以供您的 OS 运行(`./install.sh --manual macos|fedora|arch|suse|windows` 以显式选择)。
### 更新
```bash
./update.sh
```
从 GitHub 拉取最新源代码(`git pull --ff-only`)并刷新安装:Python 依赖项,Playwright 浏览器(如果安装使用系统 Chromium,则保持系统 Chromium),注册表 + 无头启动自测和重新生成的 `mcp-config.json`。在任何具有现有安装的平台上工作。您的 `data/` 目录(项目,凭证,截图,报告)永远不会被触摸。
- `./update.sh --force` - 首先隐藏对跟踪文件的本地修改(使用 `git stash pop` 恢复)
- `./update.sh --full` - 还会在 Debian/Ubuntu 上重新检查 apt 先决条件(使用 sudo)
如果您有本地修改,脚本会拒绝并列出它们而不是覆盖。
### 手动安装
```bash
# 克隆存储库
cd periscope-mcp
# 创建虚拟环境
python3 -m venv venv
source venv/bin/activate
# 安装依赖项
pip install -r requirements.txt
# 为 Playwright 安装 Chromium
playwright install chromium
```
## 安装(Docker)
```bash
docker compose up -d
```
参见下面的 [Docker 部署](#docker-部署) 部分。
## 连接 MCP 客户端
Periscope 是一个标准的 stdio MCP 服务器:任何 MCP 客户端都可以指向 `venv/bin/python server.py`,您就完成了。`./install.sh` 生成了 `mcp-config.json`,其中包含您机器的正确绝对路径;大多数客户端直接接受这种格式:
```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`
- **自定义代理** - 任何 MCP SDK 客户端都可以通过 stdio 使用相同的命令和参数启动服务器
配置完成后,请重启您的客户端。
### 教导您的代理使用工具
[`AGENTS.md`](AGENTS.md) 包含一个现成的系统提示块 - 工作流程,工具选择指导和已知陷阱。将它的内容粘贴到您的代理的系统提示(或自定义说明)中,以便它有效地驱动 63 个工具,而不是通过反复试验发现约定。
## MCP 工具参考(63 个工具)
### 项目管理(4 个工具)
| 工具 | 描述 | 必需参数 |
|------|-------------|-----------------|
| `create_project` | 创建新的测试项目 | `name`, `base_url` |
| `list_projects` | 列出所有项目 | _(无)_ |
| `get_project` | 获取项目详情 | `name` |
| `delete_project` | 删除项目 + 数据 | `name` |
### 身份验证(5 个工具)
| 工具 | 描述 | 必需参数 |
|------|-------------|-----------------|
| `set_form_login` | 配置用户名/密码表单登录 | `project`, `login_url`, `username`, `password` |
| `set_basic_auth` | 配置 HTTP 基本身份验证 | `project`, `username`, `password` |
| `set_cookies` | 注入会话 cookie | `project`, `cookies`(数组) |
| `login_project` | 使用配置的身份验证执行登录 | `project` |
| `copy_auth` | 在项目之间复制身份验证配置 + 会话 cookie | `from_project`, `to_project` |
### 静态测试(3 个工具)
| 工具 | 描述 | 必需参数 |
|------|-------------|-----------------|
| `test_url` | 测试单个 URL(截图 + 检查) | `url` |
| `crawl_project` | 从基本 URL 发现所有页面 | `project` |
| `test_project` | 完整审计:爬取 + 测试所有页面 | `project` |
### 结果(3 个工具)
| 工具 | 描述 | 必需参数 |
|------|-------------|-----------------|
| `get_screenshot` | 获取截图文件路径 | `project`, `url` |
| `list_reports` | 列出已保存的测试报告 | _(可选:`project`)_ |
| `get_report` | 读取报告文件 | `report_path` |
### 会话管理(4 个工具)
会话保留浏览器页面跨工具调用,启用多步骤交互工作流。
| 工具 | 描述 | 必需参数 |
|------|-------------|-----------------|
| `open_session` | 打开持久浏览器会话 | `url` |
| `close_session` | 关闭会话并释放资源 | `session_id` |
| `list_sessions` | 列出所有活动会话 | _(无)_ |
| `set_viewport` | 切换视口大小(8 设备预设或自定义 w/h) | `session_id` |
`set_viewport` 预设:`mobile_sm`(320x568),`mobile`(375x812),`mobile_lg`(428x926),`tablet`(768x1024),`tablet_lg`(1024x1366),`laptop`(1366x768),`desktop`(1920x1080),`desktop_lg`(2560x1440)
### 交互操作(6 个工具)
| 工具 | 描述 | 必需参数 |
|------|-------------|-----------------|
| `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` |
| `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`
### 分析(8 个工具)
| 工具 | 描述 | 必需参数 |
|------|-------------|-----------------|
| `test_form_validation` | 分析表单验证消息 | _(url 或 session_id)_ |
| `compare_screenshots` | 两个截图之间的像素差异 | `screenshot1`, `screenshot2` |
| `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 分,核心 Web Vitals,失败的审计(需要 Node.js) | `url` |
### 工作流速度(8 个工具)
| 工具 | 描述 | 必需参数 |
|------|-------------|-----------------|
| `screenshot_session` | 当前页面状态的快速截图 | `session_id` |
| `run_checks_on_session` | 对活动会话运行检查(无新页面) | `session_id` |
| `navigate_session` | 浏览器历史记录:后退、向前或重新加载 | `session_id`, `action` |
| `handle_dialog` | 接受/取消 JS 警告/确认/提示(调用前调用) | `session_id`, `action` |
| `upload_file` | 在 `<input type="|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 和发现(3 个工具)
| 工具 | 描述 | 必需参数 |
|------|-------------|-----------------|
| `web_search` | 搜索 DuckDuckGo:标题 + URL + 摘录 | `query` |
| `web_fetch` | 获取 URL,提取可读文本(或原始 HTML);默认 TLS 验证 | `url` |
| `describe_tools` | 所有工具的结构化目录,带有工作流程和提示 | _(无)_ |
## 测试检查
### 视觉 (`checks/visual.py`)
- 损坏的图像(不完整的加载或 0 自然宽度)
- 缺失的 favicon
- 水平溢出/布局问题
- 非常小的文本(< 12px)
- 缺失的 body 背景颜色
- 没有明确宽度/高度尺寸的图像
- 水平溢出 / 布局问题
- 文本过小(< 12px)
- 缺失主体背景色
- 没有显式宽度/高度尺寸的图像
### 无障碍访问(`checks/accessibility.py`)
- 缺少 `alt` 文本的图片(装饰性图片除外:`alt=""`、`role="presentation"/"none"`、`aria-hidden`)
- 没有可访问名称的链接和按钮(检查文本、`aria-label`、解析 `aria-labelledby`、`title`、`img[alt]`、SVG `<title>`;`aria-hidden` 元素豁免)
- 没有关联标签的表单输入框(`label[for]`、包裹标签、`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` 的元素
- 键盘导航审计(选项卡顺序、可见焦点指示器、元素身份循环检测)— 通过 `test_keyboard_navigation` 工具
### 功能性(`checks/functionality.py`)
- 内部链接损坏(HTTP HEAD 检查,`check_functionality` 中最多 20 个链接)
- 具有外部链接支持的综合链接检查器(最多 100 个链接)— 通过 `check_links` 工具
- 没有操作或提交按钮的表单
- 表单外的孤立按钮
- 外部链接缺少 `target="_blank"`
- 必需的表单字段计数
- 禁用自动填充输入框
### SEO(`checks/functionality.py` -> `check_seo`)
- 页面标题:缺少、太长(> 60 个字符)或太短(< 15 个字符)
- 元描述:缺少、太长(> 160 个字符)或太短(< 50 个字符)
- 缺少视口元标签
- 缺少规范 URL
- H1 标题:缺少或有多个
- Open Graph:完全缺少、不完整的核心标签(`og:title/description/image/url`)、非绝对 `og:image`、缺少 `twitter:card`
- JSON-LD 结构化数据:缺少或无法解析的块
- `noindex` via robots 元标签 **或** `X-Robots-Tag` 响应头
- robots.txt 阻止搜索引擎爬虫(Googlebot、Bingbot、DuckDuckBot 等)— 如果所有爬虫都被阻止,则报错
- 全站范围(通过 `test_project`):页面间重复的标题/元描述,在 `site_issues` 下报告
### GEO / 智能搜索(`checks/geo.py` -> `check_geo`)
生成式引擎优化 — 该网站是否可被 AI 爬虫、答案引擎和浏览器代理读取和使用:
- robots.txt 阻止 AI 爬虫(GPTBot、ClaudeBot、PerplexityBot、Google-Extended、CCBot 等 11 种)
- `llms.txt` 存在性和格式合规性(Markdown 格式,至少有一个 H1)
- WebMCP 集成:声明式 `<form toolname>` 注释存在且完整(`tooldescription`)、表单覆盖率以及当浏览器公开 `document.modelContext` 时,注册工具枚举并验证模式/名称/描述
- JSON-LD 结构化数据存在性(答案引擎引用内容)
robots.txt 和 llms.txt 仅在每个源的生命周期内获取一次并缓存。
### 性能(`checks/functionality.py` -> `get_performance_metrics`)
- DOM 内容加载时间(毫秒)
- 完整页面加载时间(毫秒)
- 首次绘制/首次内容绘制(毫秒)
- 核心 Web 重要指标(实验室值,通过缓冲的 PerformanceObserver):最大内容绘制(毫秒)、累计布局偏移、长任务的阻塞时间近似值(+ 长任务计数)
- 资源计数
- 总传输大小(字节/千字节)
对于得分,Lighthouse 官方指标使用 `run_lighthouse` 工具 — 它运行真正的 Lighthouse CLI(需要 Node.js),返回 0-100 的类别得分、官方核心 Web 重要指标和失败的审计,并将完整的 JSON 报告保存到 `data/reports/`。
## 测试输出格式
每个 `test_url` 调用返回:
```json
{
"url": "https://example.com",
"status": "success",
"status_code": 200,
"title": "页面标题",
"screenshot_path": "/path/to/screenshot.png",
"load_time_ms": 1500,
"issues": [
{
"type": "accessibility",
"severity": "error",
"message": "3 张图片缺少 alt 文本",
"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` 返回一个聚合报告,包含每页结果 + 摘要。
## 用例
### 基本测试(无身份验证)
```
用户: "测试 https://example.com 是否存在问题"
代理调用:
1. create_project(name="example", base_url="https://example.com")
2. test_project(project="example")
3. 分析结果并报告发现的问题
```
### 登录测试
```
用户: "测试 https://myapp.com,登录用户名为 admin,密码为 password123"
代理调用:
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")
```
### 基本身份验证测试
```
用户: "测试 https://staging.example.com,使用基本身份验证用户名为 admin,密码为 secret"
代理调用:
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 测试
```
用户: "使用此会话 Cookie 测试 myapp:session=abc123"
代理调用:
1. set_cookies(project="myapp", cookies=[
{"name": "session", "value": "abc123", "domain": "myapp.com"}
])
2. test_project(project="myapp")
```
### 交互式测试(基于会话)
```
用户: "前往 myapp.com,点击登录按钮,填写表单,检查发生的情况"
代理调用:
1. open_session(url="https://myapp.com") → session_id
2. get_page_elements(session_id=..., selector="button, a") → 看到可点击元素
3. click_element(session_id=..., selector="#login-btn") → 点击后截图
4. fill_form(session_id=..., fields=[
{"selector": "#email", "value": "user@test.com"},
{"selector": "#password", "value": "test123"}
], submit_selector="button[type='submit']")
5. 分析截图以查看结果
6. close_session(session_id=...)
```
### 脚本化多步骤工作流(无需会话)
```
用户: "测试 myshop.com 上的结账流程"
代理调用:
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": "test@test.com"},
{"action": "screenshot", "label": "checkout_form"},
{"action": "click", "selector": "#submit-order"}
],
run_checks=["visual", "accessibility"]
)
```
### 响应式测试
```
用户: "检查 example.com 在移动设备、平板电脑和桌面上的显示效果"
代理调用:
1. test_responsive(url="https://example.com", run_checks=["visual"])
→ 返回 375x812、768x1024 和 1920x1080 下的截图
```
### 会话期间切换视口
```
用户: "展示这个页面在移动设备上的效果"
代理调用:
1. set_viewport(session_id=..., device="mobile")
→ 返回 375x812 下的截图
```
### 通过模拟 API 测试错误处理
```
用户: "如果 API 返回 500 错误,会发生什么?"
代理调用:
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=...)
→ 显示应用如何处理错误状态
```
### 测试暗黑模式
```
用户: "该网站是否支持暗黑模式?"
代理调用:
1. open_session(url="https://example.com") → session_id
2. test_dark_mode(session_id=..., mode="dark")
→ 截图显示页面使用 prefers-color-scheme: dark
```
### 等待动态内容
```
用户: "提交此表单并等待成功消息"
代理调用:
1. fill_form(session_id=..., fields=[...], submit_selector="#submit")
2. wait_for_network(session_id=..., url_pattern="/api/submit")
3. screenshot_session(session_id=...)
```
### 在慢速网络上测试
```
用户: "这个页面在慢速连接下如何加载?"
代理调用:
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}/`** - 记录的会话视频(WebM 格式来自 Playwright)。
- **`data/diffs/`** - 截图比较差异图像。
## Docker 部署
### 构建和运行
```bash
docker compose up -d
```
### 将 MCP 客户端连接到 Docker 容器
将客户端的 MCP 配置指向容器而不是 venv:
```json
{
"mcpServers": {
"periscope": {
"command": "docker",
"args": ["exec", "-i", "periscope", "python", "/app/server.py"]
}
}
}
```
### 持久化数据
`docker-compose.yml` 将 `./data` 挂载为卷,因此截图、报告和项目配置在容器重启时存活。
## 主要设计决策
1. **每个项目的浏览器上下文** - 每个项目都有自己的 Playwright BrowserContext。这使得会话(cookie、身份验证)隔离在项目之间。
2. **延迟浏览器初始化** - Playwright 浏览器仅在第一次工具调用时启动,而不是在服务器启动时。如果浏览器崩溃或启动失败,它将在下一个调用时重新创建。
3. **BFS 爬取** - 爬取器使用带有深度跟踪的广度优先搜索。它停留在同一个域名上,并跳过非页面资源(图像、PDF 等)。
4. **检查模块化** - 每个检查类别都是 `checks/` 中的单独模块。通过创建采用 Playwright `Page` 的函数并返回 `list[dict]` 来添加新检查。
5. **JSON 存储** - 项目存储在单个 `projects.json` 文件中。对于预期规模(数十个项目,而不是数千个),不需要数据库。
6. **持久会话** - 交互式测试使用 `SessionManager`,它在会话 ID 为键的字典中保留 Playwright 页面。会话在空闲超时后自动过期,并通过配置的最大值限制以防止资源泄漏。
7. **临时 vs 会话模式** - 像 `get_page_elements`、`interact_and_test` 和 `check_links` 这样的工具接受 `session_id`(重用现有页面)或 `url`(创建临时页面并在完成后关闭)。这使它们对于交互式和一次性使用都具有灵活性。
## 添加新检查
1. 在适当的 `checks/*.py` 文件中创建函数:
```python
async def check_something(page: Page) -> list[dict]:
# 运行您的检查
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
```
2. 在 `tester.py` 中导入并调用它里面的 `test_url()`。
## 已知限制
- 没有 JavaScript SPA 路由支持(依赖 `<a href>` 进行爬取)
- 默认 `check_functionality` 链接检查限制为 20 个内部链接(使用 `check_links` 工具支持最多 100 个外部链接)
- 表单登录检测使用 CSS 选择器,可能需要针对非标准表单进行自定义
- 没有并行页面测试(页面按顺序测试)
- 交互式会话在 300s 空闲后自动过期(可通过 `SESSION_TIMEOUT` 配置)
- 最大 20 个并发会话(可通过 `MAX_SESSIONS` 配置)
- 默认的 `drag` 步骤(Playwright `drag_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 选择器 |
| 页面加载超时 | 增加 `TIMEOUT` 在 `config.py` 中或检查站点是否需要 VPN/身份验证 |
| Docker 无法访问网站 | 确保容器具有网络访问权限。使用 `network_mode: host` 如果测试 localhost |
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器