@jackwener/opencli 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/actions/setup-chrome/action.yml +26 -0
- package/.github/workflows/ci.yml +59 -3
- package/.github/workflows/e2e-headed.yml +37 -0
- package/README.md +41 -7
- package/README.zh-CN.md +23 -8
- package/SKILL.md +6 -2
- package/TESTING.md +233 -0
- package/dist/bilibili.js +2 -2
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +12 -3
- package/dist/browser.test.js +56 -16
- package/dist/interceptor.test.d.ts +4 -0
- package/dist/interceptor.test.js +81 -0
- package/dist/output.test.d.ts +3 -0
- package/dist/output.test.js +60 -0
- package/dist/pipeline/executor.js +0 -6
- package/dist/pipeline/executor.test.d.ts +4 -0
- package/dist/pipeline/executor.test.js +145 -0
- package/dist/pipeline/steps/fetch.js +4 -3
- package/dist/registry.d.ts +2 -2
- package/dist/setup.js +1 -1
- package/package.json +4 -4
- package/src/bilibili.ts +2 -2
- package/src/browser.test.ts +54 -16
- package/src/browser.ts +11 -3
- package/src/clis/twitter/notifications.ts +1 -1
- package/src/engine.ts +2 -2
- package/src/interceptor.test.ts +94 -0
- package/src/output.test.ts +69 -4
- package/src/pipeline/executor.test.ts +161 -0
- package/src/pipeline/executor.ts +0 -5
- package/src/pipeline/steps/fetch.ts +4 -3
- package/src/registry.ts +2 -2
- package/src/setup.ts +1 -1
- package/tests/e2e/browser-auth.test.ts +90 -0
- package/tests/e2e/browser-public.test.ts +169 -0
- package/tests/e2e/helpers.ts +63 -0
- package/tests/e2e/management.test.ts +106 -0
- package/tests/e2e/output-formats.test.ts +48 -0
- package/tests/e2e/public-commands.test.ts +56 -0
- package/tests/smoke/api-health.test.ts +72 -0
- package/tsconfig.json +1 -0
- package/vitest.config.ts +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Setup Chrome + xvfb
|
|
2
|
+
description: Install real Chrome and xvfb virtual display for headed browser testing
|
|
3
|
+
|
|
4
|
+
outputs:
|
|
5
|
+
chrome-path:
|
|
6
|
+
description: Path to the installed Chrome binary
|
|
7
|
+
value: ${{ steps.setup-chrome.outputs.chrome-path }}
|
|
8
|
+
|
|
9
|
+
runs:
|
|
10
|
+
using: composite
|
|
11
|
+
steps:
|
|
12
|
+
- name: Install real Chrome (stable)
|
|
13
|
+
uses: browser-actions/setup-chrome@v1
|
|
14
|
+
id: setup-chrome
|
|
15
|
+
with:
|
|
16
|
+
chrome-version: stable
|
|
17
|
+
|
|
18
|
+
- name: Verify Chrome installation
|
|
19
|
+
shell: bash
|
|
20
|
+
run: |
|
|
21
|
+
echo "Chrome path: ${{ steps.setup-chrome.outputs.chrome-path }}"
|
|
22
|
+
${{ steps.setup-chrome.outputs.chrome-path }} --version
|
|
23
|
+
|
|
24
|
+
- name: Install xvfb for headed mode
|
|
25
|
+
shell: bash
|
|
26
|
+
run: sudo apt-get install -y xvfb
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -2,12 +2,16 @@ name: CI
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
branches: [main]
|
|
5
|
+
branches: [main, dev]
|
|
6
6
|
pull_request:
|
|
7
|
-
branches: [main]
|
|
7
|
+
branches: [main, dev]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: '0 8 * * 1' # Weekly Monday 08:00 UTC — smoke tests
|
|
10
|
+
workflow_dispatch:
|
|
8
11
|
|
|
9
12
|
jobs:
|
|
10
|
-
|
|
13
|
+
# ── Fast gate: typecheck + build ──
|
|
14
|
+
build:
|
|
11
15
|
runs-on: ubuntu-latest
|
|
12
16
|
steps:
|
|
13
17
|
- uses: actions/checkout@v4
|
|
@@ -15,6 +19,7 @@ jobs:
|
|
|
15
19
|
- uses: actions/setup-node@v4
|
|
16
20
|
with:
|
|
17
21
|
node-version: '22'
|
|
22
|
+
cache: 'npm'
|
|
18
23
|
|
|
19
24
|
- name: Install dependencies
|
|
20
25
|
run: npm ci
|
|
@@ -24,3 +29,54 @@ jobs:
|
|
|
24
29
|
|
|
25
30
|
- name: Build
|
|
26
31
|
run: npm run build
|
|
32
|
+
|
|
33
|
+
# ── Unit tests (vitest shard) ──
|
|
34
|
+
unit-test:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
strategy:
|
|
37
|
+
matrix:
|
|
38
|
+
shard: [1, 2]
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- uses: actions/setup-node@v4
|
|
43
|
+
with:
|
|
44
|
+
node-version: '22'
|
|
45
|
+
cache: 'npm'
|
|
46
|
+
|
|
47
|
+
- name: Install dependencies
|
|
48
|
+
run: npm ci
|
|
49
|
+
|
|
50
|
+
- name: Run unit tests (shard ${{ matrix.shard }}/2)
|
|
51
|
+
run: npx vitest run src/ --reporter=verbose --shard=${{ matrix.shard }}/2
|
|
52
|
+
|
|
53
|
+
# ── Smoke tests (scheduled / manual only) ──
|
|
54
|
+
smoke-test:
|
|
55
|
+
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
|
56
|
+
needs: build
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
|
|
61
|
+
- uses: actions/setup-node@v4
|
|
62
|
+
with:
|
|
63
|
+
node-version: '22'
|
|
64
|
+
cache: 'npm'
|
|
65
|
+
|
|
66
|
+
- name: Install dependencies
|
|
67
|
+
run: npm ci
|
|
68
|
+
|
|
69
|
+
- name: Setup Chrome + xvfb
|
|
70
|
+
uses: ./.github/actions/setup-chrome
|
|
71
|
+
id: setup-chrome
|
|
72
|
+
|
|
73
|
+
- name: Build
|
|
74
|
+
run: npm run build
|
|
75
|
+
|
|
76
|
+
- name: Run smoke tests
|
|
77
|
+
run: |
|
|
78
|
+
xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24" \
|
|
79
|
+
npx vitest run tests/smoke/ --reporter=verbose
|
|
80
|
+
env:
|
|
81
|
+
OPENCLI_BROWSER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
|
|
82
|
+
timeout-minutes: 15
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: E2E Headed Chrome
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, dev]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
e2e-headed:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
timeout-minutes: 20
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: '22'
|
|
20
|
+
cache: 'npm'
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: npm ci
|
|
24
|
+
|
|
25
|
+
- name: Setup Chrome + xvfb
|
|
26
|
+
uses: ./.github/actions/setup-chrome
|
|
27
|
+
id: setup-chrome
|
|
28
|
+
|
|
29
|
+
- name: Build
|
|
30
|
+
run: npm run build
|
|
31
|
+
|
|
32
|
+
- name: Run E2E tests (headed Chrome + xvfb)
|
|
33
|
+
run: |
|
|
34
|
+
xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24" \
|
|
35
|
+
npx vitest run tests/e2e/ --reporter=verbose
|
|
36
|
+
env:
|
|
37
|
+
OPENCLI_BROWSER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://nodejs.org)
|
|
10
10
|
[](./LICENSE)
|
|
11
11
|
|
|
12
|
-
A CLI tool that turns **any website** into a command-line interface
|
|
12
|
+
A CLI tool that turns **any website** into a command-line interface — bilibili, zhihu, xiaohongshu, twitter, reddit, and many more — powered by browser session reuse and AI-native discovery.
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
@@ -21,6 +21,7 @@ A CLI tool that turns **any website** into a command-line interface. **59 comman
|
|
|
21
21
|
- [Built-in Commands](#built-in-commands)
|
|
22
22
|
- [Output Formats](#output-formats)
|
|
23
23
|
- [For AI Agents (Developer Guide)](#for-ai-agents-developer-guide)
|
|
24
|
+
- [Testing](#testing)
|
|
24
25
|
- [Troubleshooting](#troubleshooting)
|
|
25
26
|
- [Releasing New Versions](#releasing-new-versions)
|
|
26
27
|
- [License](#license)
|
|
@@ -46,11 +47,21 @@ OpenCLI connects to your browser through the Playwright MCP Bridge extension.
|
|
|
46
47
|
### Playwright MCP Bridge Extension Setup
|
|
47
48
|
|
|
48
49
|
1. Install **[Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm)** extension in Chrome.
|
|
49
|
-
2.
|
|
50
|
+
2. Run `opencli setup` — it auto-discovers your token and lets you choose which tools to configure:
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
```bash
|
|
53
|
+
opencli setup
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The interactive TUI will:
|
|
57
|
+
- 🔍 Auto-discover `PLAYWRIGHT_MCP_EXTENSION_TOKEN` from Chrome (no manual copy needed)
|
|
58
|
+
- ☑️ Show all detected tools (Codex, Cursor, Claude Code, Gemini CLI, etc.)
|
|
59
|
+
- ✏️ Update only the files you select (Space to toggle, Enter to confirm)
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
<details>
|
|
62
|
+
<summary>Manual setup (alternative)</summary>
|
|
63
|
+
|
|
64
|
+
Add token to your MCP client config (e.g. Claude/Cursor):
|
|
54
65
|
|
|
55
66
|
```json
|
|
56
67
|
{
|
|
@@ -66,13 +77,15 @@ First, add it to your MCP client config (e.g. Claude/Cursor):
|
|
|
66
77
|
}
|
|
67
78
|
```
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
Export in shell (e.g. `~/.zshrc`):
|
|
70
81
|
|
|
71
82
|
```bash
|
|
72
83
|
export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<your-token-here>"
|
|
73
84
|
```
|
|
74
85
|
|
|
75
|
-
|
|
86
|
+
</details>
|
|
87
|
+
|
|
88
|
+
Verify with `opencli doctor` — shows colored status for all config locations:
|
|
76
89
|
|
|
77
90
|
```bash
|
|
78
91
|
opencli doctor
|
|
@@ -84,6 +97,7 @@ opencli doctor
|
|
|
84
97
|
|
|
85
98
|
```bash
|
|
86
99
|
npm install -g @jackwener/opencli
|
|
100
|
+
opencli setup # One-time: configure Playwright MCP token
|
|
87
101
|
```
|
|
88
102
|
|
|
89
103
|
Then use directly:
|
|
@@ -118,7 +132,7 @@ npm install -g @jackwener/opencli@latest
|
|
|
118
132
|
|
|
119
133
|
| Site | Commands | Mode |
|
|
120
134
|
|------|----------|------|
|
|
121
|
-
| **bilibili** | `hot` `search` `me` `favorite`
|
|
135
|
+
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 🔐 Browser |
|
|
122
136
|
| **zhihu** | `hot` `search` `question` | 🔐 Browser |
|
|
123
137
|
| **xiaohongshu** | `search` `notifications` `feed` `me` `user` | 🔐 Browser |
|
|
124
138
|
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 🔐 Browser |
|
|
@@ -176,6 +190,24 @@ opencli cascade https://api.example.com/data
|
|
|
176
190
|
|
|
177
191
|
Explore outputs to `.opencli/explore/<site>/` (manifest.json, endpoints.json, capabilities.json, auth.json).
|
|
178
192
|
|
|
193
|
+
## Testing
|
|
194
|
+
|
|
195
|
+
See **[TESTING.md](./TESTING.md)** for the full testing guide, including:
|
|
196
|
+
|
|
197
|
+
- Current test coverage (unit + ~52 E2E tests across all 18 sites)
|
|
198
|
+
- How to run tests locally
|
|
199
|
+
- How to add tests when creating new adapters
|
|
200
|
+
- CI/CD pipeline with sharding
|
|
201
|
+
- Headless browser mode (`OPENCLI_HEADLESS=1`)
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Quick start
|
|
205
|
+
npm run build
|
|
206
|
+
npx vitest run # All tests
|
|
207
|
+
npx vitest run src/ # Unit tests only
|
|
208
|
+
npx vitest run tests/e2e/ # E2E tests
|
|
209
|
+
```
|
|
210
|
+
|
|
179
211
|
## Troubleshooting
|
|
180
212
|
|
|
181
213
|
- **"Failed to connect to Playwright MCP Bridge"**
|
|
@@ -185,6 +217,8 @@ Explore outputs to `.opencli/explore/<site>/` (manifest.json, endpoints.json, ca
|
|
|
185
217
|
- Your login session in Chrome might have expired. Open a normal Chrome tab, navigate to the target site, and log in or refresh the page to prove you are human.
|
|
186
218
|
- **Node API errors**
|
|
187
219
|
- Make sure you are using Node.js >= 18. Some dependencies require modern Node APIs.
|
|
220
|
+
- **Token issues**
|
|
221
|
+
- Run `opencli doctor` to diagnose token configuration across all tools.
|
|
188
222
|
|
|
189
223
|
## Releasing New Versions
|
|
190
224
|
|
package/README.zh-CN.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://nodejs.org)
|
|
10
10
|
[](./LICENSE)
|
|
11
11
|
|
|
12
|
-
OpenCLI
|
|
12
|
+
OpenCLI 将任何网站变成命令行工具 — B站、知乎、小红书、Twitter、Reddit 等众多站点 — 复用浏览器登录态,AI 驱动探索。
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
@@ -29,7 +29,7 @@ OpenCLI 将任何网站变成命令行工具。**59 个命令**覆盖 **18 个
|
|
|
29
29
|
|
|
30
30
|
## 亮点
|
|
31
31
|
|
|
32
|
-
-
|
|
32
|
+
- **多站点覆盖** — B站、知乎、小红书、Twitter、Reddit 等众多站点
|
|
33
33
|
- **零风控** — 复用 Chrome 登录态,无需存储任何凭证
|
|
34
34
|
- **AI 原生** — `explore` 自动发现 API,`synthesize` 生成适配器,`cascade` 探测认证策略
|
|
35
35
|
- **动态加载引擎** — 声明式的 `.yaml` 或者底层定制的 `.ts` 适配器,放入 `clis/` 文件夹即可自动注册生效
|
|
@@ -46,11 +46,21 @@ OpenCLI 通过 Playwright MCP Bridge 扩展与你的浏览器通信。
|
|
|
46
46
|
### Playwright MCP Bridge 扩展配置
|
|
47
47
|
|
|
48
48
|
1. 安装 **[Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm)** 扩展
|
|
49
|
-
2.
|
|
49
|
+
2. 运行 `opencli setup` — 自动发现 Token 并让你选择要配置哪些工具:
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
```bash
|
|
52
|
+
opencli setup
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
交互式 TUI 会:
|
|
56
|
+
- 🔍 从 Chrome 自动发现 `PLAYWRIGHT_MCP_EXTENSION_TOKEN`(无需手动复制)
|
|
57
|
+
- ☑️ 显示所有支持的工具(Codex、Cursor、Claude Code、Gemini CLI 等)
|
|
58
|
+
- ✏️ 只更新你选中的文件(空格切换,回车确认)
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
<details>
|
|
61
|
+
<summary>手动配置(备选方案)</summary>
|
|
62
|
+
|
|
63
|
+
配置你的 MCP 客户端(如 Claude/Cursor 等):
|
|
54
64
|
|
|
55
65
|
```json
|
|
56
66
|
{
|
|
@@ -66,13 +76,15 @@ OpenCLI 通过 Playwright MCP Bridge 扩展与你的浏览器通信。
|
|
|
66
76
|
}
|
|
67
77
|
```
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
在终端环境变量中导出(建议写进 `~/.zshrc`):
|
|
70
80
|
|
|
71
81
|
```bash
|
|
72
82
|
export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<你的-token>"
|
|
73
83
|
```
|
|
74
84
|
|
|
75
|
-
|
|
85
|
+
</details>
|
|
86
|
+
|
|
87
|
+
配置后运行 `opencli doctor` 检查所有位置的 Token 状态:
|
|
76
88
|
|
|
77
89
|
```bash
|
|
78
90
|
opencli doctor
|
|
@@ -84,6 +96,7 @@ opencli doctor
|
|
|
84
96
|
|
|
85
97
|
```bash
|
|
86
98
|
npm install -g @jackwener/opencli
|
|
99
|
+
opencli setup # 首次使用:配置 Playwright MCP token
|
|
87
100
|
```
|
|
88
101
|
|
|
89
102
|
直接使用:
|
|
@@ -118,7 +131,7 @@ npm install -g @jackwener/opencli@latest
|
|
|
118
131
|
|
|
119
132
|
| 站点 | 命令 | 模式 |
|
|
120
133
|
|------|------|------|
|
|
121
|
-
| **bilibili** | `hot` `search` `me` `favorite`
|
|
134
|
+
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 🔐 浏览器 |
|
|
122
135
|
| **zhihu** | `hot` `search` `question` | 🔐 浏览器 |
|
|
123
136
|
| **xiaohongshu** | `search` `notifications` `feed` `me` `user` | 🔐 浏览器 |
|
|
124
137
|
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 🔐 浏览器 |
|
|
@@ -185,6 +198,8 @@ opencli cascade https://api.example.com/data
|
|
|
185
198
|
- Chrome 里的登录态可能已经过期(甚至被要求过滑动验证码)。请打开当前 Chrome 页面,在新标签页重新手工登录或刷新该页面。
|
|
186
199
|
- **Node API 错误 (如 parseArgs, fs 等)**
|
|
187
200
|
- 确保 Node.js 版本 `>= 18`。旧版不支持我们使用的现代核心库 API。
|
|
201
|
+
- **Token 问题**
|
|
202
|
+
- 运行 `opencli doctor` 诊断所有工具的 Token 配置状态。
|
|
188
203
|
|
|
189
204
|
## 版本发布
|
|
190
205
|
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: opencli
|
|
3
3
|
description: "OpenCLI — Make any website your CLI. Zero risk, AI-powered, reuse Chrome login."
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
author: jackwener
|
|
6
6
|
tags: [cli, browser, web, mcp, playwright, bilibili, zhihu, twitter, github, v2ex, hackernews, reddit, xiaohongshu, xueqiu, AI, agent]
|
|
7
7
|
---
|
|
@@ -34,7 +34,8 @@ npm update -g @jackwener/opencli
|
|
|
34
34
|
|
|
35
35
|
Browser commands require:
|
|
36
36
|
1. Chrome browser running **(logged into target sites)**
|
|
37
|
-
2. [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) extension installed
|
|
37
|
+
2. [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) extension installed
|
|
38
|
+
3. Run `opencli setup` to auto-discover token and configure all tools
|
|
38
39
|
|
|
39
40
|
> **Note**: You must be logged into the target website in Chrome before running commands. Tabs opened during command execution are auto-closed afterwards.
|
|
40
41
|
|
|
@@ -139,6 +140,9 @@ opencli list --json # JSON output
|
|
|
139
140
|
opencli list -f yaml # YAML output
|
|
140
141
|
opencli validate # Validate all CLI definitions
|
|
141
142
|
opencli validate bilibili # Validate specific site
|
|
143
|
+
opencli setup # Interactive token setup (auto-discover + TUI checkbox)
|
|
144
|
+
opencli doctor # Diagnose token config across all tools
|
|
145
|
+
opencli doctor --fix -y # Auto-fix all config files (non-interactive)
|
|
142
146
|
```
|
|
143
147
|
|
|
144
148
|
### AI Agent Workflow
|
package/TESTING.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Testing Guide
|
|
2
|
+
|
|
3
|
+
> 面向开发者和 AI Agent 的测试参考手册。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [测试架构](#测试架构)
|
|
8
|
+
- [当前覆盖范围](#当前覆盖范围)
|
|
9
|
+
- [本地运行测试](#本地运行测试)
|
|
10
|
+
- [如何添加新测试](#如何添加新测试)
|
|
11
|
+
- [CI/CD 流水线](#cicd-流水线)
|
|
12
|
+
- [浏览器模式](#浏览器模式)
|
|
13
|
+
- [站点兼容性](#站点兼容性)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 测试架构
|
|
18
|
+
|
|
19
|
+
测试分为三层,全部使用 **vitest** 运行:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
tests/
|
|
23
|
+
├── e2e/ # E2E 集成测试(子进程运行真实 CLI)
|
|
24
|
+
│ ├── helpers.ts # runCli() 共享工具
|
|
25
|
+
│ ├── public-commands.test.ts # 公开 API 命令(无需浏览器)
|
|
26
|
+
│ ├── browser-public.test.ts # 浏览器命令(公开数据)
|
|
27
|
+
│ ├── browser-auth.test.ts # 需登录命令(graceful failure 测试)
|
|
28
|
+
│ ├── management.test.ts # 管理命令(list, validate, verify, help)
|
|
29
|
+
│ └── output-formats.test.ts # 输出格式(json/yaml/csv/md)
|
|
30
|
+
├── smoke/ # 烟雾测试(仅定时 / 手动触发)
|
|
31
|
+
│ └── api-health.test.ts # 外部 API 可用性检测
|
|
32
|
+
src/
|
|
33
|
+
├── *.test.ts # 单元测试(已有 8 个)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| 层 | 位置 | 运行方式 | 用途 |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| 单元测试 | `src/**/*.test.ts` | `npx vitest run src/` | 内部模块逻辑 |
|
|
39
|
+
| E2E 测试 | `tests/e2e/*.test.ts` | `npx vitest run tests/e2e/` | 真实 CLI 命令执行 |
|
|
40
|
+
| 烟雾测试 | `tests/smoke/*.test.ts` | `npx vitest run tests/smoke/` | 外部 API 健康 |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 当前覆盖范围
|
|
45
|
+
|
|
46
|
+
### 单元测试(8 个文件)
|
|
47
|
+
|
|
48
|
+
| 文件 | 覆盖内容 |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `browser.test.ts` | JSON-RPC、tab 管理、extension/standalone 模式切换 |
|
|
51
|
+
| `engine.test.ts` | 命令发现与执行 |
|
|
52
|
+
| `registry.test.ts` | 命令注册与策略分配 |
|
|
53
|
+
| `output.test.ts` | 输出格式渲染 |
|
|
54
|
+
| `doctor.test.ts` | Token 诊断 |
|
|
55
|
+
| `coupang.test.ts` | 数据归一化 |
|
|
56
|
+
| `pipeline/template.test.ts` | 模板表达式求值 |
|
|
57
|
+
| `pipeline/transform.test.ts` | 数据变换步骤 |
|
|
58
|
+
|
|
59
|
+
### E2E 测试(~52 个用例)
|
|
60
|
+
|
|
61
|
+
| 文件 | 覆盖站点/功能 | 测试数 |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `public-commands.test.ts` | hackernews/top, v2ex/hot, v2ex/latest, v2ex/topic | 5 |
|
|
64
|
+
| `browser-public.test.ts` | bbc, bilibili×3, weibo, zhihu×2, reddit×2, twitter, xueqiu×2, reuters, youtube, smzdm, boss, ctrip, coupang, xiaohongshu, yahoo-finance, v2ex/daily | 21 |
|
|
65
|
+
| `browser-auth.test.ts` | bilibili/me,dynamic,favorite,history,following + twitter/bookmarks,timeline,notifications + v2ex/me,notifications + xueqiu/feed,watchlist + xiaohongshu/feed,notifications | 14 |
|
|
66
|
+
| `management.test.ts` | list×5 格式, validate×3 级别, verify, --version, --help, unknown cmd | 12 |
|
|
67
|
+
| `output-formats.test.ts` | json, yaml, csv, md 格式验证 | 5 |
|
|
68
|
+
|
|
69
|
+
### 烟雾测试
|
|
70
|
+
|
|
71
|
+
公开 API 可用性(hackernews, v2ex×2, v2ex/topic)+ 全站点注册完整性检查。
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 本地运行测试
|
|
76
|
+
|
|
77
|
+
### 前置条件
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm ci # 安装依赖
|
|
81
|
+
npm run build # 编译(E2E 测试需要 dist/main.js)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 运行命令
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# 全部单元测试
|
|
88
|
+
npx vitest run src/
|
|
89
|
+
|
|
90
|
+
# 全部 E2E 测试(会真实调用外部 API)
|
|
91
|
+
npx vitest run tests/e2e/
|
|
92
|
+
|
|
93
|
+
# 单个测试文件
|
|
94
|
+
npx vitest run tests/e2e/management.test.ts
|
|
95
|
+
|
|
96
|
+
# 全部测试(单元 + E2E)
|
|
97
|
+
npx vitest run
|
|
98
|
+
|
|
99
|
+
# 烟雾测试
|
|
100
|
+
npx vitest run tests/smoke/
|
|
101
|
+
|
|
102
|
+
# watch 模式(开发时推荐)
|
|
103
|
+
npx vitest src/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 浏览器命令本地测试须知
|
|
107
|
+
|
|
108
|
+
- 无 `PLAYWRIGHT_MCP_EXTENSION_TOKEN` 时,opencli 自动启动一个独立浏览器实例
|
|
109
|
+
- `browser-public.test.ts` 使用 `tryBrowserCommand()`,站点反爬导致空数据时 warn + pass
|
|
110
|
+
- `browser-auth.test.ts` 验证 **graceful failure**(不 crash 不 hang 即通过)
|
|
111
|
+
- 如需测试完整登录态,保持 Chrome 登录态 + 设置 `PLAYWRIGHT_MCP_EXTENSION_TOKEN`,手动跑对应测试
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 如何添加新测试
|
|
116
|
+
|
|
117
|
+
### 新增 YAML Adapter(如 `src/clis/producthunt/trending.yaml`)
|
|
118
|
+
|
|
119
|
+
1. **无需额外操作**:`validate` 测试会自动覆盖 YAML 结构验证
|
|
120
|
+
2. 根据 adapter 类型,在对应文件加一个 `it()` block:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// 如果 browser: false(公开 API)→ tests/e2e/public-commands.test.ts
|
|
124
|
+
it('producthunt trending returns data', async () => {
|
|
125
|
+
const { stdout, code } = await runCli(['producthunt', 'trending', '--limit', '3', '-f', 'json']);
|
|
126
|
+
expect(code).toBe(0);
|
|
127
|
+
const data = parseJsonOutput(stdout);
|
|
128
|
+
expect(Array.isArray(data)).toBe(true);
|
|
129
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
130
|
+
expect(data[0]).toHaveProperty('title');
|
|
131
|
+
}, 30_000);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// 如果 browser: true 但可公开访问 → tests/e2e/browser-public.test.ts
|
|
136
|
+
it('producthunt trending returns data', async () => {
|
|
137
|
+
const data = await tryBrowserCommand(['producthunt', 'trending', '--limit', '3', '-f', 'json']);
|
|
138
|
+
expectDataOrSkip(data, 'producthunt trending');
|
|
139
|
+
}, 60_000);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// 如果 browser: true 且需登录 → tests/e2e/browser-auth.test.ts
|
|
144
|
+
it('producthunt me fails gracefully without login', async () => {
|
|
145
|
+
await expectGracefulAuthFailure(['producthunt', 'me', '-f', 'json'], 'producthunt me');
|
|
146
|
+
}, 60_000);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 新增管理命令(如 `opencli export`)
|
|
150
|
+
|
|
151
|
+
在 `tests/e2e/management.test.ts` 添加测试。
|
|
152
|
+
|
|
153
|
+
### 新增内部模块
|
|
154
|
+
|
|
155
|
+
在 `src/` 下对应位置创建 `*.test.ts`。
|
|
156
|
+
|
|
157
|
+
### 决策流程图
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
新增功能 → 是内部模块? → 是 → src/ 下加 *.test.ts
|
|
161
|
+
↓ 否
|
|
162
|
+
是 CLI 命令? → browser: false? → tests/e2e/public-commands.test.ts
|
|
163
|
+
↓ true
|
|
164
|
+
公开数据? → tests/e2e/browser-public.test.ts
|
|
165
|
+
↓ 需登录
|
|
166
|
+
tests/e2e/browser-auth.test.ts
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## CI/CD 流水线
|
|
172
|
+
|
|
173
|
+
### ci.yml(主流水线)
|
|
174
|
+
|
|
175
|
+
| Job | 触发条件 | 内容 |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| **build** | push/PR to main,dev | typecheck + build |
|
|
178
|
+
| **unit-test** | push/PR to main,dev | 单元测试,2 shard 并行 |
|
|
179
|
+
| **smoke-test** | 每周一 08:00 UTC / 手动 | xvfb + real Chrome,外部 API 健康检查 |
|
|
180
|
+
|
|
181
|
+
### e2e-headed.yml(E2E 测试)
|
|
182
|
+
|
|
183
|
+
| Job | 触发条件 | 内容 |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| **e2e-headed** | push/PR to main,dev | xvfb + real Chrome,全部 E2E 测试 |
|
|
186
|
+
|
|
187
|
+
E2E 使用 `browser-actions/setup-chrome` 安装真实 Chrome,配合 `xvfb-run` 提供虚拟显示器,以 headed 模式运行浏览器。
|
|
188
|
+
|
|
189
|
+
### Sharding
|
|
190
|
+
|
|
191
|
+
单元测试使用 vitest 内置 shard:
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
strategy:
|
|
195
|
+
matrix:
|
|
196
|
+
shard: [1, 2]
|
|
197
|
+
steps:
|
|
198
|
+
- run: npx vitest run src/ --shard=${{ matrix.shard }}/2
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 浏览器模式
|
|
204
|
+
|
|
205
|
+
opencli 根据 `PLAYWRIGHT_MCP_EXTENSION_TOKEN` 环境变量自动选择模式:
|
|
206
|
+
|
|
207
|
+
| 条件 | 模式 | MCP 参数 | 使用场景 |
|
|
208
|
+
|---|---|---|---|
|
|
209
|
+
| Token 已设置 | Extension 模式 | `--extension` | 本地用户,连接已登录的 Chrome |
|
|
210
|
+
| Token 未设置 | Standalone 模式 | (无特殊 flag) | CI 或无扩展环境,自启浏览器 |
|
|
211
|
+
|
|
212
|
+
CI 中使用 `OPENCLI_BROWSER_EXECUTABLE_PATH` 指定真实 Chrome 路径:
|
|
213
|
+
|
|
214
|
+
```yaml
|
|
215
|
+
env:
|
|
216
|
+
OPENCLI_BROWSER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 站点兼容性
|
|
222
|
+
|
|
223
|
+
在 GitHub Actions 美国 runner 上,部分站点因地域限制或登录要求返回空数据。E2E 测试对这些站点使用 warn + pass 策略,不影响 CI 绿灯。
|
|
224
|
+
|
|
225
|
+
| 站点 | CI 状态 | 限制原因 |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| hackernews, bbc, v2ex | ✅ 返回数据 | 无限制 |
|
|
228
|
+
| yahoo-finance | ✅ 返回数据 | 无限制 |
|
|
229
|
+
| bilibili, zhihu, weibo, xiaohongshu | ⚠️ 空数据 | 地域限制(中国站点) |
|
|
230
|
+
| reddit, twitter, youtube | ⚠️ 空数据 | 需登录或 cookie |
|
|
231
|
+
| smzdm, boss, ctrip, coupang, xueqiu | ⚠️ 空数据 | 地域限制 / 需登录 |
|
|
232
|
+
|
|
233
|
+
> 使用 self-hosted runner(国内服务器)可解决地域限制问题。
|
package/dist/bilibili.js
CHANGED
|
@@ -63,10 +63,10 @@ export async function apiGet(page, path, opts = {}) {
|
|
|
63
63
|
return fetchJson(page, url);
|
|
64
64
|
}
|
|
65
65
|
export async function fetchJson(page, url) {
|
|
66
|
-
const
|
|
66
|
+
const urlJs = JSON.stringify(url);
|
|
67
67
|
return page.evaluate(`
|
|
68
68
|
async () => {
|
|
69
|
-
const res = await fetch(
|
|
69
|
+
const res = await fetch(${urlJs}, { credentials: "include" });
|
|
70
70
|
return await res.json();
|
|
71
71
|
}
|
|
72
72
|
`);
|
package/dist/browser.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export declare class Page implements IPage {
|
|
|
50
50
|
selectTab(index: number): Promise<void>;
|
|
51
51
|
networkRequests(includeStatic?: boolean): Promise<any>;
|
|
52
52
|
consoleMessages(level?: string): Promise<any>;
|
|
53
|
-
scroll(direction?: string,
|
|
53
|
+
scroll(direction?: string, _amount?: number): Promise<void>;
|
|
54
54
|
autoScroll(options?: {
|
|
55
55
|
times?: number;
|
|
56
56
|
delayMs?: number;
|
package/dist/browser.js
CHANGED
|
@@ -169,7 +169,7 @@ export class Page {
|
|
|
169
169
|
async consoleMessages(level = 'info') {
|
|
170
170
|
return this.call('tools/call', { name: 'browser_console_messages', arguments: { level } });
|
|
171
171
|
}
|
|
172
|
-
async scroll(direction = 'down',
|
|
172
|
+
async scroll(direction = 'down', _amount = 500) {
|
|
173
173
|
await this.call('tools/call', { name: 'browser_press_key', arguments: { key: direction === 'down' ? 'PageDown' : 'PageUp' } });
|
|
174
174
|
}
|
|
175
175
|
async autoScroll(options = {}) {
|
|
@@ -303,6 +303,7 @@ export class PlaywrightMCP {
|
|
|
303
303
|
return new Promise((resolve, reject) => {
|
|
304
304
|
const isDebug = process.env.DEBUG?.includes('opencli:mcp');
|
|
305
305
|
const debugLog = (msg) => isDebug && console.error(`[opencli:mcp] ${msg}`);
|
|
306
|
+
const useExtension = !!process.env.PLAYWRIGHT_MCP_EXTENSION_TOKEN;
|
|
306
307
|
const extensionToken = process.env.PLAYWRIGHT_MCP_EXTENSION_TOKEN;
|
|
307
308
|
const tokenFingerprint = getTokenFingerprint(extensionToken);
|
|
308
309
|
let stderrBuffer = '';
|
|
@@ -344,7 +345,9 @@ export class PlaywrightMCP {
|
|
|
344
345
|
executablePath: process.env.OPENCLI_BROWSER_EXECUTABLE_PATH,
|
|
345
346
|
});
|
|
346
347
|
if (process.env.OPENCLI_VERBOSE) {
|
|
347
|
-
console.error(`[opencli]
|
|
348
|
+
console.error(`[opencli] Mode: ${useExtension ? 'extension' : 'standalone'}`);
|
|
349
|
+
if (useExtension)
|
|
350
|
+
console.error(`[opencli] Extension token: fingerprint ${tokenFingerprint}`);
|
|
348
351
|
}
|
|
349
352
|
debugLog(`Spawning node ${mcpArgs.join(' ')}`);
|
|
350
353
|
this._proc = spawn('node', mcpArgs, {
|
|
@@ -559,7 +562,13 @@ function appendLimited(current, chunk, limit) {
|
|
|
559
562
|
return next.slice(-limit);
|
|
560
563
|
}
|
|
561
564
|
function buildMcpArgs(input) {
|
|
562
|
-
const args = [input.mcpPath
|
|
565
|
+
const args = [input.mcpPath];
|
|
566
|
+
if (!process.env.CI) {
|
|
567
|
+
// Local: always connect to user's running Chrome via MCP Bridge extension
|
|
568
|
+
args.push('--extension');
|
|
569
|
+
}
|
|
570
|
+
// CI: standalone mode — @playwright/mcp launches its own browser (headed by default).
|
|
571
|
+
// xvfb provides a virtual display for headed mode in GitHub Actions.
|
|
563
572
|
if (input.executablePath) {
|
|
564
573
|
args.push('--executable-path', input.executablePath);
|
|
565
574
|
}
|