dsh-agent-team-ui-testing 0.1.2 → 0.1.3
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/README.md +2 -1
- package/client.js +24 -10
- package/docs/getting-started.md +1 -1
- package/docs/images/tutorial-01-overview.png +0 -0
- package/docs/images/tutorial-02-history-dropdown.png +0 -0
- package/docs/images/tutorial-03-pick-element.png +0 -0
- package/docs/images/tutorial-04-annotate.png +0 -0
- package/docs/index.md +1 -0
- package/docs/tutorial.md +125 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -256,7 +256,7 @@ dsh plugin --profile web add github:bloodarea/dsh-agent-team-ui-testing#vX.Y.Z
|
|
|
256
256
|
|
|
257
257
|
1. Install the package into the DSH profile being used.
|
|
258
258
|
2. Restart DSH after the first install so the plugin bundle is registered.
|
|
259
|
-
3. Open the debug view from **更多 → 调试视图**, or from the plugin card in the right sidebar.
|
|
259
|
+
3. Open the debug view from **更多 → 调试视图**, or from the plugin card in the right sidebar. **First time here? The [screenshot walkthrough](docs/tutorial.md) takes you from opening the panel to a verified agent action in seven short steps.**
|
|
260
260
|
4. Changes to `client.js` only need a panel refresh; changes to `index.js`, the tool surface or bundled skills need a DSH restart.
|
|
261
261
|
|
|
262
262
|
For plugin development, link or copy this repository into the profile instead of installing the npm package.
|
|
@@ -289,6 +289,7 @@ A typical agent flow is: navigate to the target, inspect state with `browser_sta
|
|
|
289
289
|
| File | Contents |
|
|
290
290
|
| --- | --- |
|
|
291
291
|
| [docs/index.md](docs/index.md) | Documentation index |
|
|
292
|
+
| [docs/tutorial.md](docs/tutorial.md) | **Beginner walkthrough (with screenshots)**: open the panel, load a page, inspect, click, pick an element, annotate and collect evidence |
|
|
292
293
|
| [docs/getting-started.md](docs/getting-started.md) | Requirements, install, debug panel and first calls |
|
|
293
294
|
| [docs/tools.md](docs/tools.md) | All 37 tools, parameters and error classes |
|
|
294
295
|
| [docs/capability-tiers.md](docs/capability-tiers.md) | T1–T4 selection model, P0 infrastructure and known limits |
|
package/client.js
CHANGED
|
@@ -10,7 +10,7 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
|
|
11
11
|
const ID = 'ui-testing-browser';
|
|
12
12
|
/** 面板构建标识:刷新后用它确认浏览器里跑的是哪一份 client.js(排查「改了没生效」)。 */
|
|
13
|
-
const CLIENT_BUILD = '2026-11-
|
|
13
|
+
const CLIENT_BUILD = '2026-11-11T18:50+08:00·address-history-per-workspace';
|
|
14
14
|
const KIND = 'debug-view';
|
|
15
15
|
const TITLE = '调试视图';
|
|
16
16
|
/** 可选运行环境;本期只有 Web 视图可用,其余置灰。 */
|
|
@@ -62,7 +62,12 @@ window.__ModuleLoader__.load({
|
|
|
62
62
|
* real implementations rather than a copy.
|
|
63
63
|
*/
|
|
64
64
|
const ADDRESS_HISTORY_LIMIT = 7;
|
|
65
|
-
const ADDRESS_HISTORY_KEY = 'dsh-agent-team-ui-testing:address-history:
|
|
65
|
+
const ADDRESS_HISTORY_KEY = 'dsh-agent-team-ui-testing:address-history:v2';
|
|
66
|
+
/** One bucket per workspace scope: two workspaces never read each other's history. */
|
|
67
|
+
function addressHistoryStorageKey(scopeId) {
|
|
68
|
+
const clean = typeof scopeId === 'string' ? scopeId.trim().slice(0, 96).replace(/[^A-Za-z0-9_.:-]/gu, '') : '';
|
|
69
|
+
return ADDRESS_HISTORY_KEY + ':' + (clean || 'unscoped');
|
|
70
|
+
}
|
|
66
71
|
function addressHistoryNormalize(list, limit = ADDRESS_HISTORY_LIMIT) {
|
|
67
72
|
const out = [];
|
|
68
73
|
for (const item of Array.isArray(list) ? list : []) {
|
|
@@ -91,16 +96,17 @@ window.__ModuleLoader__.load({
|
|
|
91
96
|
function addressHistoryStorage() {
|
|
92
97
|
try { return typeof window !== 'undefined' && window.localStorage ? window.localStorage : null; } catch { return null; }
|
|
93
98
|
}
|
|
94
|
-
function addressHistoryRead(storage, limit = ADDRESS_HISTORY_LIMIT) {
|
|
99
|
+
function addressHistoryRead(storage, scopeId, limit = ADDRESS_HISTORY_LIMIT) {
|
|
95
100
|
try {
|
|
96
101
|
if (!storage) return [];
|
|
97
|
-
|
|
102
|
+
const key = addressHistoryStorageKey(scopeId);
|
|
103
|
+
return addressHistoryNormalize(JSON.parse(storage.getItem(key) || '[]'), limit);
|
|
98
104
|
} catch { return []; }
|
|
99
105
|
}
|
|
100
|
-
function addressHistoryWrite(storage, list) {
|
|
106
|
+
function addressHistoryWrite(storage, scopeId, list) {
|
|
101
107
|
try {
|
|
102
108
|
if (!storage) return false;
|
|
103
|
-
storage.setItem(
|
|
109
|
+
storage.setItem(addressHistoryStorageKey(scopeId), JSON.stringify(addressHistoryNormalize(list)));
|
|
104
110
|
return true;
|
|
105
111
|
} catch { return false; }
|
|
106
112
|
}
|
|
@@ -3087,18 +3093,26 @@ window.__ModuleLoader__.load({
|
|
|
3087
3093
|
* addresses, newest first. Recorded from the *accepted* address, so a redirect or an Agent
|
|
3088
3094
|
* navigation is stored as the page that actually loaded, and a failed navigation is not.
|
|
3089
3095
|
* -------------------------------------------------------------------------------------- */
|
|
3090
|
-
|
|
3096
|
+
// History is per workspace: the key carries the runtime's opaque workspace scope id, so a
|
|
3097
|
+
// second workspace never sees (or inherits) another workspace's addresses. An unresolved
|
|
3098
|
+
// scope reads nothing and never writes, instead of falling back to a shared bucket.
|
|
3099
|
+
const historyScopeId = ui && ui.scope && ui.scope.id ? String(ui.scope.id) : '';
|
|
3100
|
+
const [addressHistory, setAddressHistory] = React.useState(() => addressHistoryRead(addressHistoryStorage(), historyScopeId));
|
|
3091
3101
|
const [historyOpen, setHistoryOpen] = React.useState(false);
|
|
3092
3102
|
const historyAddress = acceptedAddress();
|
|
3093
3103
|
React.useEffect(() => {
|
|
3104
|
+
setAddressHistory(addressHistoryRead(addressHistoryStorage(), historyScopeId));
|
|
3105
|
+
}, [historyScopeId]);
|
|
3106
|
+
React.useEffect(() => {
|
|
3107
|
+
if (!historyScopeId) return;
|
|
3094
3108
|
if (!/^https?:\/\//i.test(historyAddress)) return;
|
|
3095
3109
|
const storage = addressHistoryStorage();
|
|
3096
|
-
const current = addressHistoryRead(storage);
|
|
3110
|
+
const current = addressHistoryRead(storage, historyScopeId);
|
|
3097
3111
|
const next = addressHistoryPush(current, historyAddress);
|
|
3098
3112
|
if (next.length === current.length && next[0] === current[0]) return;
|
|
3099
|
-
addressHistoryWrite(storage, next);
|
|
3113
|
+
addressHistoryWrite(storage, historyScopeId, next);
|
|
3100
3114
|
setAddressHistory(next);
|
|
3101
|
-
}, [historyAddress]);
|
|
3115
|
+
}, [historyAddress, historyScopeId]);
|
|
3102
3116
|
// The dropdown is a local popover: it never survives a mode switch or a scope change.
|
|
3103
3117
|
React.useEffect(() => {
|
|
3104
3118
|
if (!historyOpen) return undefined;
|
package/docs/getting-started.md
CHANGED
|
@@ -77,7 +77,7 @@ browser_screenshot {out} # 留一张可复核的 PNG
|
|
|
77
77
|
- **内部标签页**:真人点击 `target=_blank` / `window.open` 时,面板会在地址栏下方新增内部标签并自动切过去,原页面保留为另一个标签;标签栏由「标注」左侧开关控制显隐,单标签时也显示并可新建;`+` 只在标签栏内,工具栏不常驻。Agent 工具默认操作各自会话的 selectedTab(无选择 primary),可用 `browser_tabs` 查看/选择;GUI 切页不改 Agent 选择。自动弹窗与未授权弹窗仍被阻断。
|
|
78
78
|
- **调试工具**:「更多 → 打开调试工具」为当前页面请求一个独立窗口,加载 Chrome 自带的 DevTools(与 F12 等价)。需要当前工作区的浏览器运行时已启动;只有 GUI 与 DSH 同机、且通过 loopback(127.0.0.1/localhost)访问时可用,远程 GUI 暂不支持。弹窗被浏览器拦截时,面板会显示提示和「在新标签页打开调试工具」链接。
|
|
79
79
|
- **共享状态**:面板与 Agent 使用同一 `expectedRevision`,冲突会明确报告而不是静默覆盖。
|
|
80
|
-
- **地址栏**:地址栏右侧的时钟按钮展开「最近访问」下拉,按栈展示最近 7 条已访问的 HTTP(S) 地址(最近的在上;重复访问只置顶不重复;空地址、`about:blank`
|
|
80
|
+
- **地址栏**:地址栏右侧的时钟按钮展开「最近访问」下拉,按栈展示最近 7 条已访问的 HTTP(S) 地址(最近的在上;重复访问只置顶不重复;空地址、`about:blank` 与应用自身地址不记录)。条目点击直接跳到该地址。历史按**工作区隔离**:存进浏览器 `localStorage` 的 `dsh-agent-team-ui-testing:address-history:v2:<工作区作用域 id>`(同一工作区的多个面板共享,不同工作区互不可见),最多 7 条;作用域尚未解析时不读也不写,旧版本共用的 `v1` 历史不会迁移。鼠标点下拉外部或按 <kbd>Esc</kbd> 收起,切换作用域或进入标注模式也会收起。地址栏与标签页按钮之间的 **转到** 按钮等同按 <kbd>Enter</kbd> 提交当前地址(同一套校验与错误提示,标注模式或导航中禁用)。
|
|
81
81
|
|
|
82
82
|
## 设置页
|
|
83
83
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/index.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
| 文档 | 内容 |
|
|
8
8
|
| --- | --- |
|
|
9
|
+
| [新手图文教程](tutorial.md) | 从打开调试视图到让 Agent 完成一次可复核的操作:加载页面、观察、操作、选元素、标注与取证,全部带截图 |
|
|
9
10
|
| [快速开始](getting-started.md) | 安装要求、安装与接入、打开面板与首次调用 |
|
|
10
11
|
| [诊断状态码(中英对照)](diagnose-reason-codes.md) | 诊断页每个状态码的英文机器契约与中文界面文案、出现阶段、动态码族与维护方式 |
|
|
11
12
|
| [工具参考](tools.md) | 37 个工具的参数、返回与错误分类 |
|
package/docs/tutorial.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# 新手图文教程:从打开调试视图到让 Agent 真正动手
|
|
2
|
+
|
|
3
|
+
这篇教程只用**一个本地静态页**演示完整闭环:打开调试视图 → 加载页面 → 让 Agent 看见并操作 → 拿到可复核的证据。全程不需要写代码,也不需要配置模型。
|
|
4
|
+
|
|
5
|
+
> 截图来自一个**只装了本插件**的隔离 DSH 实例(独立 `DSH_HOME`、临时端口、一次性 Chromium),所以画面里没有其它插件的影响。
|
|
6
|
+
|
|
7
|
+
## 0. 前置条件(一次性)
|
|
8
|
+
|
|
9
|
+
1. 已安装 DSH,并且本机有 Chrome / Chromium(插件不自带浏览器)。
|
|
10
|
+
2. 把插件装进要用的 profile,然后**重启一次 DSH**:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
dsh plugin --profile web add dsh-agent-team-ui-testing
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
3. 确认工具面在:随便开一个会话,问 Agent「你现在有哪些 browser_ 工具」,应能列出 37 个(T1 程序化、T2 结构化 GUI、T3 静态视觉、T4 动态行为、网络 P0、多标签页、标注)。
|
|
17
|
+
|
|
18
|
+
## 1. 打开调试视图
|
|
19
|
+
|
|
20
|
+
两个入口,效果相同:
|
|
21
|
+
|
|
22
|
+
- 左侧 **更多 → 调试视图**;
|
|
23
|
+
- 右侧边栏的插件卡片。
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
从上到下依次是:
|
|
28
|
+
|
|
29
|
+
| 区域 | 用途 |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| 顶部一行 | 后退 / 前进 / 刷新、**地址栏**、最近访问、转到、标签页、标注、尺寸调整、选元素、更多 |
|
|
32
|
+
| 标签栏 | 一个标签一个页面;真人点击页面里的 `target=_blank` 会在这里新开一个 |
|
|
33
|
+
| 预览区 | 目标页的实时画面,鼠标键盘都可以直接操作 |
|
|
34
|
+
| 底部状态栏 | 空闲 / Agent 操作中、帧率、帧间隔 |
|
|
35
|
+
|
|
36
|
+
## 2. 在地址栏打开要调试的页面
|
|
37
|
+
|
|
38
|
+
把地址粘进地址栏按 <kbd>Enter</kbd>,或点它右边的 **转到** 按钮(等价于回车)。这里用 `http://127.0.0.1:8199/index.html` 这个演示页。
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
- **红色框 = 最近访问**:点开下拉,按栈展示最近 7 条访问过的地址(最近的在上,重复访问只置顶)。历史**按工作区隔离**,换个工作区就是另一份记录;点任意一条直接跳过去。
|
|
43
|
+
- **蓝色框 = 转到**:等同回车提交地址栏里的内容,校验与报错跟回车完全一致。
|
|
44
|
+
- **橙色框 = 下拉展开后的样子**:标题里的 `3/7` 表示已有 3 条、上限 7 条。
|
|
45
|
+
|
|
46
|
+
页面加载后,预览区就是**真的**那个页面的实时画面:在这里点击、输入、滚轮滚动,页面收到的都是真实事件。
|
|
47
|
+
|
|
48
|
+
## 3. 让 Agent 先「看」,再「动」
|
|
49
|
+
|
|
50
|
+
新手最容易犯的错是让 Agent 直接点。正确顺序是**先观察、再定位、最后动作**,典型对话:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
你:打开 http://127.0.0.1:8199/index.html,看看页面上有哪些控件
|
|
54
|
+
Agent:browser_navigate { url } → browser_snapshot
|
|
55
|
+
(拿到 ref 列表:textbox "订单号" [ref=e2]、button "查询" [ref=e3] …)
|
|
56
|
+
你:在订单号里填 A-1001,然后点查询
|
|
57
|
+
Agent:browser_fill_form { fields: [{ ref: "e2", type: "textbox", value: "A-1001" }] }
|
|
58
|
+
browser_click { ref: "e3" }
|
|
59
|
+
browser_node_state / browser_eval 回读结果
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
三条经验:
|
|
63
|
+
|
|
64
|
+
1. **先 snapshot 拿 ref**:ref 形如 `e2`,每次导航或刷新都会失效,失效就重新 snapshot。
|
|
65
|
+
2. **动作后必须回读**:`browser_click` 返回 `dispatched:true` 只说明事件发出去了,页面是否真的变了要自己再读一次。
|
|
66
|
+
3. **点不动是结论,不是障碍**:元素被遮挡、持续运动、在 iframe 里,工具会明确报 `NOT_INTERACTABLE`,不要换个坐标硬点。
|
|
67
|
+
|
|
68
|
+
## 4. 把元素直接发给 Agent:选元素
|
|
69
|
+
|
|
70
|
+
不知道元素叫什么、又不想手写 selector 时,用工具栏的**选元素**:
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
1. 点工具栏的准星图标进入选元素模式(页面零输入,滚轮仍可滚动);
|
|
75
|
+
2. 鼠标划过预览,被命中的元素会出现蓝框与 `标签#id` 标签;
|
|
76
|
+
3. 点一下,元素信息会作为 `@` 引用塞进聊天框,直接继续提问即可;
|
|
77
|
+
4. <kbd>Esc</kbd> 退出。
|
|
78
|
+
|
|
79
|
+
## 5. 画标注、截图给 Agent
|
|
80
|
+
|
|
81
|
+
需要指位置时别用文字描述——直接画:
|
|
82
|
+
|
|
83
|
+

|
|
84
|
+
|
|
85
|
+
- 标注按**画布坐标**锚定,可以画在画面外的空白带上;
|
|
86
|
+
- 标注模式下页面不接收指针与键盘输入,<kbd>Esc</kbd> 退出;
|
|
87
|
+
- 工具条最后的**截图**按钮,会把「当前画面 + 已提交标注」合成一张 PNG **直接放进聊天框**,Agent 立刻能看到你圈的是哪块;
|
|
88
|
+
- Agent 也能反过来读/写这份标注文档(`browser_annotation`),但只有你正在调试视图里看着这个标签页时才允许写。
|
|
89
|
+
|
|
90
|
+
## 6. 需要证据时
|
|
91
|
+
|
|
92
|
+
| 你想要 | 用哪个 |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| 一张可复核的截图 | `browser_screenshot { out: "output/capture/step1.png" }` |
|
|
95
|
+
| 断言颜色/位置/差异 | T3:`browser_vision_image` / `browser_vision_compare` / `browser_vision_check` |
|
|
96
|
+
| 接口为什么 404/500、请求发了几次 | 网络 P0:`browser_network_session start` → `browser_network_events` / `browser_network_detail` |
|
|
97
|
+
| 动画卡不卡、有没有闪烁 | T4:`browser_dynamic_session` → `browser_motion_observe` → `browser_motion_analyze` |
|
|
98
|
+
| 页面变了什么 | `browser_snapshot` 前后对比,`browser_state` 看 URL/标题/视口 |
|
|
99
|
+
|
|
100
|
+
## 7. 常见问题
|
|
101
|
+
|
|
102
|
+
**点了页面没反应?**
|
|
103
|
+
先看工具栏:**标注**、**选元素**、**尺寸调整**三者互斥,任一开启时页面不接收普通输入。<kbd>Esc</kbd> 或再点一次按钮退出。
|
|
104
|
+
|
|
105
|
+
**画面提示「画面同步中,暂时无法操作」?**
|
|
106
|
+
视口刚改过尺寸,面板在等新帧对齐(最长约 1 秒),等下或点「重试同步」。这期间工具会拒绝派发,而不是点到错的位置。
|
|
107
|
+
|
|
108
|
+
**地址栏历史里没有我刚在别的窗口访问的地址?**
|
|
109
|
+
历史按工作区隔离,不同工作区各有一份,这是设计行为。
|
|
110
|
+
|
|
111
|
+
**改了 `client.js` 没生效?**
|
|
112
|
+
面板要刷新一次;改 `index.js`、工具 schema 或内置 Skill 则需要重启 DSH。
|
|
113
|
+
|
|
114
|
+
**面板里看不到原生弹窗(alert / 文件选择器)?**
|
|
115
|
+
它们由浏览器 UI 层绘制,不在页面画面里,面板无法显示也无法操作。
|
|
116
|
+
|
|
117
|
+
**Agent 说页面在「别的标签页」?**
|
|
118
|
+
每个 Agent 会话有自己的选中标签,互不抢占;用 `browser_tabs` 查看和切换,真人切标签不会改变 Agent 的选择。
|
|
119
|
+
|
|
120
|
+
## 下一步
|
|
121
|
+
|
|
122
|
+
- [快速开始](getting-started.md):安装、设置页、环境选择
|
|
123
|
+
- [工具参考](tools.md):37 个工具的参数与错误分类
|
|
124
|
+
- [能力分层与限制](capability-tiers.md):什么时候该用 T1 / T2 / T3 / T4
|
|
125
|
+
- [选元素加入聊天框](element-picker.md)、[视口与标注](viewport-control.md)、[网络调试](network-p0.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agent-team-ui-testing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Frontend GUI debugging and testing for DeepSeekHarness agents: Web/Chromium is implemented with 37 browser_* tools for structured GUI automation, visual comparison, dynamic frame analysis, annotation read/write and network debugging; Windows/Linux/macOS desktop, Android, TUI, HarmonyOS, mini programs, games and 3D (nine environments) are planned.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"./locale/*.json": "./locale/*.json",
|
|
14
14
|
"./icon": "./icon.png"
|
|
15
15
|
},
|
|
16
|
-
"files": ["index.js", "client.js", "select-control.js", "context-menu.js", "frame-source.js", "frame-push.js", "browser-safety.js", "capture-p0.js", "annotation-capture.js", "vision-t3.js", "vision-baseline.js", "network-p0.js", "dynamic-t4.js", "cordis.patch.yml", "locale/*.json", "icon.png", "banner.png", "banner_zh.png", "docs/*.md", "core/", "targets/", "integration/", "skills/**"],
|
|
16
|
+
"files": ["index.js", "client.js", "select-control.js", "context-menu.js", "frame-source.js", "frame-push.js", "browser-safety.js", "capture-p0.js", "annotation-capture.js", "vision-t3.js", "vision-baseline.js", "network-p0.js", "dynamic-t4.js", "cordis.patch.yml", "locale/*.json", "icon.png", "banner.png", "banner_zh.png", "docs/*.md", "docs/images/", "core/", "targets/", "integration/", "skills/**"],
|
|
17
17
|
"dsh": {
|
|
18
18
|
"bundle": { "patch": "./cordis.patch.yml" },
|
|
19
19
|
"client": { "platform": "web", "immediately": true, "inject": [] }
|