Agent Skills › driceroland/Search › search-bench

search-bench

GitHub

驱动 Search macOS 浏览器进行自动化测试,支持多环境隔离、页面交互及扩展管理。

skill/search-bench/SKILL.md driceroland/Search

触发场景

用户要求测试 Search 功能 需要驱动浏览器执行操作 运行 ./bench 命令 打开 Search 页面或截图 检查面板或测试扩展

安装

npx skills add driceroland/Search --skill search-bench -g -y
更多选项

非标准路径

npx skills add https://github.com/driceroland/Search/tree/main/skill/search-bench -g -y

不安装直接使用

npx skills use driceroland/Search@search-bench

指定 Agent (Claude Code)

npx skills add driceroland/Search --skill search-bench -a claude-code -g -y

安装 repo 全部 skill

npx skills add driceroland/Search --all -g -y

预览 repo 内 skill

npx skills add driceroland/Search --list

SKILL.md

Frontmatter
{
    "name": "search-bench",
    "metadata": {
        "short-description": "Drive Search with .\/bench"
    },
    "description": "Drive the Search macOS browser from the repo-root .\/bench command: open flask-marked bench tabs, wait for load, read text, run JavaScript, click, type, submit, screenshot the page, probe window chrome, and install or press Chrome extensions. Use when the user asks to test Search, drive the browser, run .\/bench, open a page in Search, screenshot a tab, check a panel, or exercise an extension, and when they run \/search-bench.\n"
}

Search bench

Search is a native Mac browser in this repo. Drive the running app with ./bench from the repo root. ./bench help is the command syntax. This file is how to use it without touching the browser a person has open.

Which process

Every command goes to one world. Pass the same flag on every call.

Flag Whose browser Socket folder
--test World test ~/Library/Application Support/Search (test)/
--world NAME World NAME (lowercase letters, digits, hyphens) ~/Library/Application Support/Search (NAME)/
none The installed browser they actually use ~/Library/Application Support/Search/

Use a test world for any work that changes chrome, installs or removes extensions, resizes, sends real key events, or selects a tab. A swift build binary under .build/ is always world test, even with no SEARCH_PROBE. ./fresh.sh launches build/Search.app with SEARCH_PROBE set, which is also a test world.

select, key, resize, and ext-answer fail on the installed browser. --yes skips an extension's install dialog only on a test run.

On the installed browser, only when they asked you to drive that window: tabs, probe, and page commands against bench tabs. Leave ui, select, key, resize, and every ext-* command for a test world unless they named that change on their own browser. look and sidebar are saved preferences.

Get a test world listening

One process per world. Quit a process only after its executable path is this repo's build/Search.app or a .build/ binary, or its environment contains SEARCH_PROBE. Leave /Applications/Search.app alone. killall, quitting by the name Search, and osascript quit hit the installed app too: same bundle id, same process name. A SEARCH_DEV=1 ./build.sh build shows as Search-<branch> on a yellow icon in the Dock and ⌘Tab (see build.sh), but that name is only what people see: its process is still Search, so go by the executable path.

  1. ./bench --test tabs (or --world NAME). A tab list means that world is listening. Do not launch another.
  2. If it prints Search isn't listening:
    • Nothing running for that world: defaults write SUITE bench -bool true, then ./fresh.sh again. For a named world, SEARCH_PROBE=NAME ./fresh.sh again.
    • A test process is up but the socket is dead: write the default, terminate that pid, then ./fresh.sh again. The switch is read at launch.
  3. Retry ./bench --test tabs until it prints. Launch takes a moment.
  4. The suite is com.officecommun.search.test, or com.officecommun.search.test.NAME for a named world.

./fresh.sh with no argument deletes that world's folder, settings suite, and WebKit store, then opens it. The suite delete clears the bench switch, so a wipe has to be followed by the defaults write, a quit of the process it just opened, and ./fresh.sh again. Wipe only when they asked for a clean browser.

If ./bench tabs (no flag) is not listening, ask them to turn on Settings › General › Let a script drive Search. Do not write defaults for the installed app.

./fresh.sh builds build/Search.app when that bundle is missing. It opens a world whose bench switch is on, hidden, since a test run that comes to the front trips the probe's guard and then refuses every command for the rest of that run. Write the switch before launching, not after, or the world opens in front and has to be thrown away.

Tabs that are not theirs

./bench tabs prints one row per tab. ⚗ is a bench tab. ● is the tab they are on. A trailing … is still loading. A trailing z is asleep.

  • open appends a bench tab and prints its id. Keep that id.
  • Pass the id tabs printed. Matching is a prefix of the tab's UUID. The first match wins.
  • close ID refuses anything except a bench tab. close all closes every bench tab, including ones another script opened. Close the ids you opened.
  • go, click, type, submit, eval, text, shot, and sleep act on whichever tab id you pass. Pass a ⚗ id unless they named one of their tabs.
  • Bench tabs are not selected, not stored in the session, and not written to history. A bench page you have not selected is laid out off-screen at 1280×800. That is the page shot captures.
  • Close the ids you opened when you finish, including when a later command fails. Turning the socket off closes leftover bench tabs.
  • Send one ./bench call at a time to a world. A command that never answers is cut off at about 25 seconds. wait uses the seconds you pass (default 20) plus a few.

A page

id=$(./bench --test open https://example.com)
./bench --test wait "$id" 20
./bench --test text "$id"
./bench --test shot "$id" "$TMPDIR/search-bench.png"
./bench --test close "$id"

open and go take an address, not a search phrase. No spaces. Schemes: http, https, file, about, data. A host with no scheme becomes https://, except localhost, *.localhost, and LAN addresses, which become http://.

wait prints JSON. Continue when loading is false. timeout: true means it was still loading. failure is the load error. Give a slow page a larger second argument.

text is document.body.innerText, cut at 120000 characters. A cut sets truncated and prints [… truncated] on stderr. If the text is empty, eval document.readyState and location.href before treating the page as blank.

eval ID JS takes the script as one quoted argument. The printed value is JSON, or a string when the result is not JSON.

click, type, and submit take one CSS selector, resolved with document.querySelector. Quote it. type sets the control's value and fires input and change (a contenteditable gets textContent and an input event). submit submits the form around the element, or the element when it is a form. The reply is {"ok": true} or an error: nothing matched, or no form.

shot prints the PNG path. Read that file. It is the web view, not the tab bar or the window. Pass a path under $TMPDIR, which is this account's own: /tmp is shared, and a picture of a signed-in page is not something to leave where anything else running here can write over it. An optional last argument is the snapshot width in points.

go ID URL loads a new address in a bench tab you already have.

Chrome

probe prints the window as JSON: panels (settings, welcome, passwords, history, downloads, bookmarks), whether the address field is open, modal title, look, appearance, the key window, every window's frame, and traffic-light positions. It also reports sidebar, sidePosition (left or right), sideWidth, and activePageFrame (x, y, width, height in points from the window's top-left) when the active page is in the main window. Use it for chrome. shot cannot see chrome.

ui KEY VALUE changes chrome and answers {"ok": true}. On a test world unless they asked for it on theirs.

Key Value
settings passwords welcome history downloads bookmarks hidden sidebar extensions on or off
side left or right
look light, dark, or system

extensions on opens the puzzle-button menu. ext-menu PATH writes that menu to a PNG. look, sidebar, and side are remembered.

resize WIDTH HEIGHT [STEPS] (test only) drags the window to that size and returns the size and traffic-light positions. key ID TEXT (test only) sends real key events to a tab and returns how many the page did not use. sleep ID tries to sleep a tab now and reports the reason it stayed awake. A bench tab stays awake.

Extensions

Needs macOS 15.4 or later. Do this on a test world. Ids here are extension ids from ./bench extensions, not tab ids.

ext-add takes a Chrome Web Store link or id. ext-folder takes a folder that contains manifest.json. Both return {"started": true} before the install finishes. --yes is required on a test run or the app waits on a confirm dialog.

./bench --test ext-add 'https://chromewebstore.google.com/detail/…' --yes
./bench --test extensions

Poll extensions until busy is "". Then read loaded, errors, and reported for that id.

ext-private ID on|off (test only) lets one loaded extension into private tabs, or out. extensions reports it as private, which stays false for a manifest with "incognito": "not_allowed". Only private tabs opened after some extension was let in carry extensions.

ext-press ID opens its popup. While that popup is the one open, ext-popup ID JS runs JavaScript in it and ext-shot ID PATH writes a PNG. ext-page ID [PATH] opens one of its own pages in a bench tab, prints that tab id, and eval there runs with the extension's APIs. ext-reload ID loads it again. A folder extension is copied in fresh. ext-pin ID [on|off], ext-enable ID on|off, and ext-remove ID change that world. ext-answer yes|no|ask (test only) answers later permission questions and lists what was asked.

From an MCP client

./bench-mcp is the same bench as MCP tools, for an agent that has it registered (claude mcp add search-bench -- /path/to/Search/bench-mcp). It only drives test worlds it started itself: open_session with no world starts build/Search.app hidden in a fresh agent-… world and wipes it on close_session; with world it starts that test world and keeps it. A world that is already open, by fresh.sh, a test or anything else, gets world_in_use, and a second controller on the same world gets world_busy. Every request checks that the socket is still held by the process the session started; if not, not_ours.

  • open_tab returns a page handle (p2); read_page returns element handles (p2.s1.e4) with role, name, value and box. The next read_page, or the page going elsewhere, ends them: stale_element means read again.
  • click is a real click (the bench's tap) after checking the element is attached, visible and not covered (obscured names what covers it). type sets the field and reports verified; keys: true types real key events. Password and card fields are never read back.
  • wait_for takes text, url, selector, role, name, gone; with none, it waits for the load. A timeout is an error and repeats nothing.
  • bench sends any other socket request to the session's world, with a page handle allowed in id. quit is refused: close the session instead.

Prefer it to shell calls when the client has it: the lease, the cleanup and the handle checks come with it.

Failures

The script exits non-zero and prints error: …. Trust that string.

  • isn't listening — the process is down, or the switch is off. See above.
  • no tab — the id is stale. Run tabs.
  • not a bench tab — close was aimed at their tab.
  • only works on a --test run, or only in a test run — select, key, resize, or ext-answer was aimed at the installed browser.
  • no popup open — ext-press that id, then shot or eval the popup.
  • unknown command — run ./bench help. The script is ahead of this file.
  • open needs a url — the string was not an address. See the scheme rules above.

版本历史

  • 87d8516 当前 2026-11-02 09:41

    新增 MCP server 以通过 bench 接口提供服务

  • 8bf85ad 2026-10-18 15:58

    文档语法修正;修复 bench 驱动世界启动时意外前置问题;支持侧边栏位置设置;优化截图存储路径。

  • 491f321 2026-09-27 10:24

元信息

文件数
0
版本
87d8516
Hash
fe801cb0
收录时间
2026-09-27 10:24

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-11-05 00:46
浙ICP备14020137号-1