---
name: qqbrowser-skill
description: Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, or automating any browser task.
source: https://pypi.org/project/qqbrowser-skill/
homepage: https://browser.qq.com/
permissions:
  - network: Required for browser navigation and web page interaction
  - filesystem: Required for downloading files and saving screenshots to temporary directories
---

# qqbrowser-skill

## Installation

```bash
# Linux / macOS
pipx install qqbrowser-skill && qqbrowser-skill install

# Windows
pip install qqbrowser-skill && qqbrowser-skill install
```

## Key Concepts

- **Element Index**: Encoded string like `2_sfli_qp0u` (format: `highlightIndex_attrHash_xpathHash`). Generated by `browser_snapshot`, used to target elements for interaction. Features a 4-level degradation matching mechanism for cross-snapshot stability.
- **Snapshot**: Returns page content with indexed elements. Re-snapshot is required after any DOM change (navigation, form submission, AJAX loading).
- **Task Recording**: All browser tasks MUST be wrapped with `task_begin`/`task_end` to enable replay.

## Core Workflow

```bash
qqbrowser-skill task_begin --description "描述任务"     # 1. Start recording (MANDATORY)
qqbrowser-skill browser_go_to_url --url <url>           # 2. Navigate
qqbrowser-skill browser_snapshot                        # 3. Get element indices
# ... interact using indices ...                        # 4. Perform actions
qqbrowser-skill browser_snapshot                        # 5. Re-snapshot after DOM changes
qqbrowser-skill task_end                                # 6. End recording (MANDATORY)
```

## Command Reference

### Navigation
```bash
browser_go_to_url --url <url>          # Navigate to URL
browser_go_back                         # Go back
browser_wait --seconds <n>              # Wait (default 3s)
```

### Snapshot & Screenshot
```bash
browser_snapshot                        # Element indices mode (for interaction)
browser_snapshot --markdown             # Clean Markdown mode (for reading, no indices)
browser_screenshot [--full] [--annotate]  # Screenshot (returns .webp temp path)
```

### Click & Input
```bash
browser_click_element --index <id>                     # Click
browser_dblclick_element --index <id>                  # Double-click
browser_focus_element --index <id>                     # Focus
browser_input_text --index <id> --text "<content>"     # Clear + input text
```

### Scroll
```bash
browser_scroll_down [--amount <px>]     # Scroll down (default one page)
browser_scroll_up [--amount <px>]       # Scroll up
browser_scroll_to_text --text "<text>"  # Scroll to text
browser_scroll_to_top / browser_scroll_to_bottom
browser_scroll_by --direction <dir> --pixels <n> [--index <id>]
browser_scroll_into_view --index <id>
```

### Keyboard
```bash
browser_keypress --key <key>                           # Press key (Enter, Tab, etc.)
browser_keyboard_op --action type --text "<content>"   # Type text
browser_keyboard_op --action inserttext --text "<content>"  # Insert without key events
browser_keydown --key <key> / browser_keyup --key <key>
```

### Dropdown & Checkbox
```bash
browser_get_dropdown_options --index <id>
browser_select_dropdown_option --index <id> --text "<option>"
browser_check_op --index <id> --value / --no-value
```

### Find and Act (Semantic Locators)
```bash
browser_find_and_act --by <role|text|label|placeholder|testid|css> --value "<v>" --action <click|fill|type> [--actionValue "<v>"] [--name "<n>"] [--nth <n>]
# --nth: select the Nth matching element (0-based). Essential for loop patterns in lists.
```

### Get Information & State
```bash
browser_get_info --type <text|url|title|html|value|attr|count|box|styles|list_selector> [--index <id>] [--attribute <name>]
browser_check_state --state <visible|enabled|checked> --index <id>
```

> **💡 Key Usage**: `browser_get_info` returns **only the requested info** (no page state or interactive elements), making it lightweight and token-efficient.
>
> **🔑 `list_selector` — Auto-detect CSS selector for list iteration (PREFERRED for loop tasks):**
> ```bash
> # From snapshot you see a list of articles:
> #   [3_abc1_def2]<a 人工智能的未来/>
> #   [4_xyz3_uvw4]<a 深度学习入门/>
> #   [5_mno5_pqr6]<a 大模型时代/>
>
> # Pick ANY one element from the list and run:
> browser_get_info --type list_selector --index "3_abc1_def2"
> # → {"success":true, "selector":".ContentItem h2 a", "count":10,
> #    "samples":["人工智能的未来","深度学习入门","大模型时代","...","..."],
> #    "strategy":"parent_level_1"}
>
> # Now use this selector directly in find_and_act for loop iteration:
> browser_find_and_act --by css --value ".ContentItem h2 a" --action click --nth 1
> ```
> **This is the RECOMMENDED way to find CSS selectors for list operations.** It replaces the manual workflow of `get_info --type html` → analyze → construct selector → probe verify. One command does it all.
>
> If `list_selector` returns `success: false`, fall back to manual inspection:
> ```bash
> browser_get_info --type html --index "5_abc1_def2"
> # → returns element HTML, use it to construct a selector manually
> ```

### JavaScript Evaluation
```bash
browser_eval_content_js --script "<js_code>"           # Evaluate JS, return result
browser_eval_content_js --script "<base64>" --base64   # Base64-encoded script
```

### Download
```bash
browser_download_file --index <id>     # Download by clicking element
browser_download_url                    # Download from URL
```

### Tab Management
```bash
browser_tab_open --url <url>           # Open new tab
browser_tab_list                        # List tabs
browser_tab_switch --tabId <n>         # Switch tab
browser_tab_close --tabId <n>          # Close tab
```

### Dialog
```bash
browser_dialog --action <accept|dismiss> [--text "<input>"]
```

### Replay
```bash
browser_replay --script <path>         # Replay from file
browser_replay --script_content '<json>'  # Replay from JSON string
browser_replay --script <path> --variables '{"key":"value"}'  # With params
```

> **⚠️ LONG EXECUTION TIME**: `browser_replay` executes ALL steps in the playbook sequentially (navigation, waiting, data extraction, etc.). **This can take up to 10 minutes** depending on the number of steps, network conditions, and configured delays/timeouts.
>
> **You MUST wait patiently** for the command to complete — do NOT assume it has hung or timed out. Do NOT interrupt or retry unless you receive an explicit error message.
> Typical durations: simple playbooks (5-10 steps) ~30s–1min, complex playbooks with loops (20+ steps) ~3–10min.

### Task Recording
```bash
task_begin --description "<description>"   # Start recording (MUST be first)
task_end                                    # Stop recording → then auto-generate playbook (see below)
task_latest                                 # Get most recent recording (for playbook generation)
```

> **IMPORTANT**: After `task_end`, do NOT just show the raw recording path. The raw recording (`~/.qqbrowser-skill/records/task_*.json`) is NOT a ready-to-use replay script. You MUST proceed to generate a proper playbook — see "Generating Replay Scripts / Playbooks" section.

### Utility
```bash
browser_done --success --text "<msg>"  # Mark task complete
status                                  # Check skill status
list                                    # List available skills
```

---

## ⚠️ MANDATORY: Replay-Friendly Execution Rules

**All operations MUST prioritize replayability. Follow these rules to ensure recorded steps can be replayed with parameterized variables.**

### Rule 1: Task Classification

Before executing, classify the task:

| Category | Replayable? | Strategy |
|----------|-------------|----------|
| **A: Fixed-path** (navigate, click, fill forms) | ✅ | Standard commands, all recorded |
| **B: Data extraction** | ⚠️ Partial | Use `browser_eval_content_js` (replayable), NOT `snapshot --markdown` + AI summarization |
| **C: Content understanding** (summarize, compare) | ❌ | Separate from replayable steps, mark with `"requires_ai": true` |
| **D: Dynamic iteration** (pagination, load more) | ⚠️ | Use fixed number of operations based on predictable calculation |

### Rule 2: Data Extraction — Always Prefer JS

```bash
# ✅ Replayable: JS extraction
browser_eval_content_js --script "JSON.stringify(Array.from(document.querySelectorAll('.item')).slice(0,10).map(el=>({title:el.querySelector('.title')?.textContent?.trim()})))" --base64

# ❌ Non-replayable: AI reading
browser_snapshot --markdown  # then AI summarizes
```

**Exception**: Tasks explicitly requiring AI understanding (e.g., "总结文章要点") — note in `task_begin` description: "需要AI在线参与，不可纯回放".

### Rule 3: Analyze First, Execute Once (No Trial-and-Error Recording)

**CRITICAL**: Do NOT use trial-and-error during a recorded task. Every command between `task_begin` and `task_end` is recorded — failed attempts pollute the recording and make it unreplayable.

**Correct workflow for data extraction:**
```bash
task_begin --description "..."
browser_go_to_url --url "..."
browser_wait --seconds 3
browser_snapshot                    # ← AI analyzes page structure here (not recorded in replay)
# AI identifies the correct, stable selector BEFORE writing the JS script
browser_eval_content_js --script "/* one correct script */"   # ← Only this gets replayed
task_end
```

**PROHIBITED patterns:**
- ❌ Running multiple `browser_eval_content_js` with different selectors hoping one works
- ❌ Using `browser_get_info` to "check" results mid-recording then adjusting
- ❌ Hardcoding element index strings (e.g., `[id='25_tg4y_yb9z']`) inside JS scripts — index is for command params only, not JS selectors
- ❌ Using regex to match page text content as a selector strategy (fragile, locale-dependent)

**Rule**: Use `browser_snapshot` to understand the DOM, then write **one definitive** JS extraction script. If the first script fails, call `task_end` to discard, fix the script, then re-record with `task_begin`.

### Rule 4: JS Selector Priority

| Priority | Type | Example |
|----------|------|---------|
| 1 | `id` | `#rank-list` |
| 2 | `data-*` | `[data-testid="item"]` |
| 3 | ARIA | `[role="listitem"]` |
| 4 | Semantic class | `.article-title` |
| 5 | Structural path | `main > ul > li` |
| ❌ | Dynamic/hash class | `.css-1a2b3c` — NEVER use |

### Rule 5: Repeating Patterns & Pagination

**Identify repeating patterns during recording**. If you find yourself doing the same sequence of actions N times (e.g., click item → read → go back), this is a **loop pattern**. Record it correctly:

**Recording strategy for loop tasks:**
```bash
task_begin --description "搜索{{topic}}并提取前{{count}}篇文章内容"
browser_go_to_url --url "https://example.com/search?q=AI"
browser_wait --seconds 3
browser_snapshot                           # AI analyzes list structure

# ★ STEP 0: DISCOVER LIST SELECTOR (MANDATORY before any list iteration)
# Use list_selector to auto-detect the CSS selector from any list item's snapshot index:
browser_get_info --type list_selector --index "3_abc1_def2"
# → {"success":true, "selector":".result-item h2 a", "count":10, "samples":[...]}
# Then record the probe with the discovered selector for playbook generation:
browser_eval_content_js --script "JSON.stringify({__list_probe__: true, selector: '.result-item h2 a', count: document.querySelectorAll('.result-item h2 a').length, samples: Array.from(document.querySelectorAll('.result-item h2 a')).slice(0,3).map(e=>e.textContent.trim())})"
# The __list_probe__ marker helps the playbook generator locate this data.

# Record ONE complete iteration as the pattern:
browser_find_and_act --by css --value ".result-item h2 a" --action click --nth 1
browser_wait --seconds 2
browser_eval_content_js --script "JSON.stringify({title: document.querySelector('h1')?.textContent, content: document.querySelector('.content')?.textContent?.substring(0,2000)})"
browser_go_back
browser_wait --seconds 1

# Record second iteration to confirm the pattern:
browser_find_and_act --by css --value ".result-item h2 a" --action click --nth 2
browser_wait --seconds 2
browser_eval_content_js --script "JSON.stringify({title: document.querySelector('h1')?.textContent, content: document.querySelector('.content')?.textContent?.substring(0,2000)})"
browser_go_back

task_end
```

**Key points:**
- **★ MUST discover and probe list selector** before iterating — first use `browser_get_info --type list_selector --index <id>` to auto-detect the CSS selector, then use `browser_eval_content_js` with `__list_probe__: true` to record it. **NEVER guess CSS selectors.**
- Record **at least 2 iterations** so the playbook generator can identify the loop pattern
- Use `browser_find_and_act` with `--nth` (not `browser_click_element` with hardcoded index) for list items — this enables loop parameterization
- The playbook generator will automatically convert repetitions into a `loop` structure with `{{count}}` controlling iterations

**How to find the CSS selector (MANDATORY — use `list_selector`):**
```
★ PREFERRED (one command, auto-detect):
1. browser_snapshot              → AI sees list elements: [3_abc1_def2]<a 人工智能的未来/> ...
2. browser_get_info --type list_selector --index "3_abc1_def2"
                                 → returns: {"success":true, "selector":".ContentItem h2 a", "count":10, "samples":[...]}
3. Use the returned selector     → proceed with __list_probe__ and find_and_act

FALLBACK (if list_selector returns success=false):
1. browser_get_info --type html --index "3_abc1_def2"
                                 → returns: <a class="ContentItem-title" href="/p/123456">人工智能的未来</a>
2. AI reads the HTML             → identifies class, parent structure, etc.
3. AI constructs CSS selector    → ".ContentItem-title" or "a[data-za-detail-view-element_name='Title']"
4. browser_eval_content_js       → run __list_probe__ to verify the selector
```
**NEVER guess CSS selectors.** Always use `list_selector` first, then fall back to `html` inspection if needed.

**For simple pagination** (next page button):
```bash
browser_eval_content_js --script "/* extract page 1 */"
browser_find_and_act --by text --value "下一页" --action click
browser_wait --seconds 2
browser_eval_content_js --script "/* extract page 2 */"
```

**DO NOT** use AI judgment loops ("check if enough, scroll more").

### Rule 6: Multi-Tab Recording Strategy

When a task involves links that open in new tabs (e.g., `target="_blank"`), record using **explicit tab management commands** instead of relying on `browser_go_back`:

```bash
task_begin --description "搜索并提取文章(新Tab场景)"
browser_go_to_url --url "https://example.com/search?q=AI"
browser_wait --seconds 3
browser_snapshot                           # AI analyzes list structure

# ★ DISCOVER + PROBE LIST SELECTOR (same as Rule 5 — MANDATORY before list iteration):
browser_get_info --type list_selector --index "3_abc1_def2"   # auto-detect selector
browser_eval_content_js --script "JSON.stringify({__list_probe__: true, selector: '.result-item h2 a', count: document.querySelectorAll('.result-item h2 a').length, samples: Array.from(document.querySelectorAll('.result-item h2 a')).slice(0,3).map(e=>e.textContent.trim())})"

# Click link that opens in new tab:
browser_find_and_act --by css --value ".result-item h2 a" --action click --nth 1
browser_tab_list                           # AI checks: new tab appeared?
browser_tab_switch --tabId <new_tab_id>    # Switch to the new tab
browser_wait --seconds 2
browser_eval_content_js --script "..."     # Extract content in detail page
browser_tab_close --tabId <current_tab_id> # Close detail tab
browser_tab_switch --tabId <origin_tab_id> # Switch back to list page
browser_wait --seconds 1

# Repeat for second item...
browser_find_and_act --by css --value ".result-item h2 a" --action click --nth 2
# ... same pattern ...

task_end
```

**Key points:**
- Use `browser_tab_list` to observe tab changes (this is AI-only, filtered from playbook)
- Record physical `tabId` during recording — the **playbook generator** will convert them to semantic references (`"origin"`, `"latest"`, `"current"`)
- **DO NOT use `browser_go_back`** when the previous action opened a new tab — `go_back` navigates within the current tab's history, it cannot close tabs or switch between them
- Record **at least 2 iterations** of the tab pattern (same as loop recording) so the playbook generator can detect and convert to `loop` + tab management

**How to detect new tab during recording:**
1. After clicking a link, call `browser_tab_list`
2. If a new tab appeared (tab count increased), use `browser_tab_switch` to go to it
3. If no new tab (same count), the link navigated in-place → use normal flow with `browser_go_back`

### Rule 7: Fixed vs Variable Parameters

| Command | Fixed | Variable (→ `{{param}}`) |
|---------|-------|--------------------------|
| `browser_go_to_url` | Base URL | Query params, path segments |
| `browser_click_element` | `--index` | — (never parameterize) |
| `browser_input_text` | `--index` | `--text` |
| `browser_select_dropdown_option` | `--index` | `--text` |
| `browser_keyboard_op` | `--action` | `--text` |
| `browser_eval_content_js` | Script structure | Count, selector, keyword in script |
| `browser_find_and_act` | `--by`, `--action` | `--value`, `--actionValue`, `--name` |
| `browser_dialog` | `--action` | `--text` |
| `browser_wait` / `browser_scroll_*` | All params | — |

### Rule 8: Non-Replayable Commands (AI-Decision Only)

These commands help AI decide but produce **no replayable action** — they are skipped during replay:

`browser_snapshot`, `browser_snapshot --markdown`, `browser_screenshot`, `browser_get_info`, `browser_check_state`, `browser_get_dropdown_options`, `browser_tab_list`

**Principle**: AI uses these to decide; the resulting **actions** (click, input, select) are what gets recorded and replayed.

### Rule 9: Prefer `browser_find_and_act` for Dynamic Content

For elements in dynamic lists (search results, feeds) where index stability is uncertain:

```bash
# ✅ Preferred: semantic locator (stable across page changes)
browser_find_and_act --by text --value "{{target_text}}" --action click

# ⚠️ Acceptable: index-based (for stable page structures only)
browser_click_element --index 15_abc1_def2
```

---

## Generating Replay Scripts / Playbooks

### Auto-Generate Trigger

After every `task_end`, if the task description indicates it should be replayable (e.g., contains "可回放", "replay", or is a standard operational task without AI summarization), the AI **MUST automatically**:

1. Load the `qqbrowser-playbook` skill
2. Run `task_latest` to get the raw recording
3. Generate a clean playbook (filtering out AI-only commands, parameterizing user data)
4. Save to `~/.qqbrowser-skill/playbooks/<name>.json`

**DO NOT** simply tell the user to run `browser_replay --script <raw_recording_path>`. Raw recordings contain trial-and-error steps and AI-only commands — they are NOT replay-ready.

### Explicit Trigger

When the user explicitly asks to "save as replay script" / "generate playbook" / "保存成回放脚本" / "生成回放脚本":

Same workflow as above.

### Rules

1. **MUST** load the `qqbrowser-playbook` skill for format specification
2. **MUST** use `task_latest` to get recorded steps — do NOT fabricate
3. **MUST** filter out all AI-only commands (`browser_snapshot`, `browser_screenshot`, `browser_get_info`, `browser_check_state`, etc.)
4. **MUST** save to `~/.qqbrowser-skill/playbooks/<name>.json` (macOS/Linux) or `%LOCALAPPDATA%/qqbrowser-skill/playbooks/<name>.json` (Windows)
5. **MUST** use the exact JSON format from the `qqbrowser-playbook` skill specification
6. **MUST NOT** output the raw recording path as the "replay script" — it is source material, not a ready playbook
