---
name: rab-cdp-debug
description: 用于指导 Agent 通过 Chrome DevTools MCP 的 `evaluate_script` 工具，结合 `window.__RS_ROOT_CONTAINER__` API 对 `@rabjs/react` 应用进行逻辑验证。当用户提到 Service 逻辑验证、Chrome DevTools 调试 Service、CDP 操控 rsjs Service、浏览器页面中调试 Service 实例 时，应优先使用这个 skill。适用场景包括：使用 chrome-devtools-mcp 的 evaluate_script 访问 Service 实例、验证 Service 状态、调用 Service 方法、通过 listServices 枚举当前页面所有 Service。
version: 0.2.10-beta.0
npm: '@rabjs/devtools'
sourcePath: packages/react
repository: git@github.com:ximing/rab.git
---

# window.__RS_ROOT_CONTAINER__ 调试指南（Chrome DevTools MCP）

本 skill 告知 Agent 如何通过 **Chrome DevTools MCP** 的 `evaluate_script` 工具，利用 `@rabjs/devtools` 挂载的 `window.__RS_ROOT_CONTAINER__` 能力，对 Service 层进行功能验证与状态检查。

---

## 前置条件：初始化挂载

`window.__RS_ROOT_CONTAINER__` **不会自动挂载**，必须由应用入口显式初始化。

1. 安装依赖：

```bash
pnpm add @rabjs/devtools
```

2. 在应用入口（如 `main.tsx`）调用一次：

```ts
import { setupWindowRootContainer } from '@rabjs/devtools';

setupWindowRootContainer();
```

如果目标页面尚未接入，先引导用户完成上述初始化步骤，再进行后续调试。SSR 安全：非浏览器环境下调用会自动跳过。

---

## 能力概述

`window.__RS_ROOT_CONTAINER__` 是 `setupWindowRootContainer()` 在浏览器环境下挂载的全局访问句柄，暴露整棵容器树的查询接口。

**挂载时机**：应用入口调用 `setupWindowRootContainer()` 时。

### 容器树结构

```
global (getGlobalContainer())           ← 真正的根，与 React 无关
  └─ RSRootInner_1                      ← RSRoot 的 bindServices 容器
       └─ ProductPage_2                 ← 页面级 bindServices 容器
            └─ CartDomain_3             ← Domain 级 bindServices 容器
```

### RSRootContainerHandle 接口

```ts
interface RSRootContainerHandle {
  container: Container;                    // global 容器实例
  getService(instanceId: string): Service | undefined;
  getContainer(containerName: string): Container | undefined;
  listServices(): Array<{
    instanceId: string;       // 格式: ClassName_nanoid
    containerName: string;    // 来自 bindServices options.name 或自动生成
    identifierLabel: string;  // Service 类名
    instance: Service;        // 内存对象引用，可直接操控
  }>;
}
```

---

## evaluate_script 工具使用说明

Chrome DevTools MCP 提供 `evaluate_script` 工具，可在当前选中页面内执行 JavaScript 函数，**返回值必须是 JSON 可序列化的**。

```
function (string) (required): JavaScript 函数声明
  示例无参数: () => { return document.title }
  示例有参数: (el) => { return el.innerText; }
args (array) (optional): 传入函数的参数列表
```

> **关键约束**：Service 实例本身不可序列化跨进程传递，必须在 `evaluate_script` 内部完成操作，只将基础类型（数字、字符串、布尔、普通对象）作为结果返回。

---

## 常用调试操作

### 1. 检查 handle 是否已挂载

```js
() => {
  return typeof window.__RS_ROOT_CONTAINER__;
}
// 期望返回: "object"
```

### 2. 列出所有已实例化的 Service

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return { error: '__RS_ROOT_CONTAINER__ 未挂载' };
  return handle.listServices().map(s => ({
    instanceId: s.instanceId,
    containerName: s.containerName,
    identifierLabel: s.identifierLabel,
  }));
}
```

返回示例：

```json
[
  { "instanceId": "CartService_abc12", "containerName": "ProductPage_2", "identifierLabel": "CartService" },
  { "instanceId": "UserService_xyz99", "containerName": "RSRootInner_1", "identifierLabel": "UserService" }
]
```

### 3. 通过 instanceId 获取 Service 状态

先通过 `listServices` 获得 `instanceId`，再读取 Service 内部状态：

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return { error: '__RS_ROOT_CONTAINER__ 未挂载' };
  const svc = handle.getService('CartService_abc12');
  if (!svc) return { error: '未找到指定 Service' };
  // 返回可序列化的状态字段
  return {
    itemCount: svc.items?.length ?? 0,
    total: svc.total,
  };
}
```

### 4. 通过 identifierLabel 查找 Service

当不知道具体 `instanceId` 时，先通过类名找到：

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return null;
  const entry = handle.listServices().find(s => s.identifierLabel === 'CartService');
  if (!entry) return { error: 'CartService 未找到' };
  return {
    instanceId: entry.instanceId,
    containerName: entry.containerName,
  };
}
```

### 5. 调用 Service 方法并验证结果

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return { error: '__RS_ROOT_CONTAINER__ 未挂载' };
  const entry = handle.listServices().find(s => s.identifierLabel === 'CartService');
  if (!entry) return { error: 'CartService 未找到' };
  const svc = entry.instance;
  // 调用方法
  svc.addItem({ id: 'test-1', name: 'Test Product', price: 9.9 });
  // 返回可序列化的验证结果
  return {
    itemCount: svc.items.length,
    lastItem: svc.items[svc.items.length - 1],
  };
}
```

### 6. 调用异步 Service 方法

```js
async () => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return { error: '__RS_ROOT_CONTAINER__ 未挂载' };
  const entry = handle.listServices().find(s => s.identifierLabel === 'ProductService');
  if (!entry) return { error: 'ProductService 未找到' };
  const svc = entry.instance;
  await svc.fetchProducts();
  return {
    productCount: svc.products.length,
    loadingState: svc.loadingState,
  };
}
```

### 7. 通过 containerName 查找特定容器内的 Service

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return null;
  return handle.listServices()
    .filter(s => s.containerName === 'ProductPage_2')
    .map(s => ({ instanceId: s.instanceId, identifierLabel: s.identifierLabel }));
}
```

### 8. 重置 Service 状态（验证后清理）

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return { error: '__RS_ROOT_CONTAINER__ 未挂载' };
  const entry = handle.listServices().find(s => s.identifierLabel === 'CartService');
  if (!entry) return { error: '未找到' };
  entry.instance.clearCart?.();
  return { cleared: true, itemCount: entry.instance.items?.length ?? 0 };
}
```

---

## 典型验证流程

**Step 1**：确认 handle 存在

```js
() => typeof window.__RS_ROOT_CONTAINER__
// 期望: "object"
```

**Step 2**：枚举所有 Service，找到目标

```js
() => window.__RS_ROOT_CONTAINER__?.listServices().map(s => ({
  instanceId: s.instanceId,
  identifierLabel: s.identifierLabel,
  containerName: s.containerName,
}))
```

**Step 3**：取出 Service 当前状态快照

```js
() => {
  const svc = window.__RS_ROOT_CONTAINER__?.getService('CartService_abc12');
  return svc ? { total: svc.total, count: svc.items.length } : null;
}
```

**Step 4**：触发操作

```js
() => {
  const entry = window.__RS_ROOT_CONTAINER__?.listServices().find(s => s.identifierLabel === 'CartService');
  entry?.instance.addItem({ id: '1', name: 'Apple', price: 5 });
  return { ok: true };
}
```

**Step 5**：验证状态变更

```js
() => {
  const svc = window.__RS_ROOT_CONTAINER__?.getService('CartService_abc12');
  return { total: svc?.total, count: svc?.items.length };
}
```

---

## 断言用法

`@rabjs/devtools` 提供了 **RSExpectBuilder** 链式断言 API，可以直接通过 `window.__RS_ROOT_CONTAINER__.expect(instanceId)` 创建，也可以通过独立函数 `rsExpect(instance)` 使用。

### 核心概念

- **懒执行**：`toBe()` / `toExist()` 等方法只记录断言，不立即执行
- **链式调用**：所有断言方法返回 `this`，可无限链式调用
- **三种执行模式**：
  - `.run()` — 返回结构化结果对象，不抛错
  - `.check()` — 控制台输出彩色报告，返回 `boolean`
  - `.expect()` — 失败时抛出 `RSAssertionError`（类 Jest 语义）

### 路径语法

所有断言方法的 `path` 参数支持点号路径，用于深层属性访问：

```
'total'              → svc.total
'items.length'       → svc.items.length
'user.profile.name'  → svc.user.profile.name
```

---

### 断言方法速查表

| 方法 | 说明 | op |
|------|------|-----|
| `.toBe(path, expected)` | `actual === expected` | `eq` |
| `.notToBe(path, expected)` | `actual !== expected` | `neq` |
| `.toBeGreaterThan(path, n)` | `actual > n` | `gt` |
| `.toBeGreaterThanOrEqual(path, n)` | `actual >= n` | `gte` |
| `.toBeLessThan(path, n)` | `actual < n` | `lt` |
| `.toBeLessThanOrEqual(path, n)` | `actual <= n` | `lte` |
| `.toBeBetween(path, lo, hi)` | `lo <= actual <= hi` | `between` |
| `.toExist(path)` | `actual != null` | `exists` |
| `.toNotExist(path)` | `actual == null` | `notExists` |
| `.toInclude(path, item)` | 数组/字符串包含 | `includes` |
| `.toNotInclude(path, item)` | 数组/字符串不包含 | `notIncludes` |
| `.toMatch(path, pattern)` | 正则匹配 | `matches` |
| `.toBeType(path, type)` | `typeof actual === type` | `type` |
| `.toHaveLength(path, n)` | `actual.length === n` | `length` |
| `.toHaveLengthGt(path, n)` | `actual.length > n` | `lengthGt` |
| `.toHaveLengthGte(path, n)` | `actual.length >= n` | `lengthGte` |
| `.toHaveLengthLt(path, n)` | `actual.length < n` | `lengthLt` |
| `.toHaveLengthLte(path, n)` | `actual.length <= n` | `lengthLte` |
| `.toHaveKeys(path, keys)` | 对象包含所有指定 key | `hasKeys` |
| `.toMatchObject(path, subset)` | 对象浅层匹配子集 | `matchObject` |
| `.toDeepEqual(path, expected)` | JSON 深比较 | `deepEq` |
| `.toHaveSome(path, assertion)` | 数组中至少一项满足 | `some` |
| `.toHaveEvery(path, assertion)` | 数组所有项满足 | `every` |

---

### 基本示例：控制台调试（`.check()`）

控制台快速验证，带彩色报告输出，返回 `boolean`：

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return { error: '未挂载' };
  return handle
    .expect('CartService_abc123')
    .describe('购物车基础验证')
    .toBe('items.length', 3)
    .toExist('currentUser')
    .toBeGreaterThan('total', 0)
    .check(); // 控制台输出报告，返回 true/false
}
```

> **注意**：`.check()` 直接向控制台打印，`evaluate_script` 只需返回布尔值即可。

---

### 获取结构化结果（`.run()`）

`.run()` 返回可 JSON 序列化的结构化结果，适合 evaluate_script 跨进程传递：

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return { error: '未挂载' };
  const result = handle
    .expect('CartService_abc123')
    .toBe('items.length', 3)
    .toBeGreaterThan('total', 0)
    .toExist('currentUser')
    .run();
  // 返回可序列化的结构化断言结果
  return {
    passed: result.passed,
    summary: result.summary,   // { passed: 2, total: 3 }
    results: result.results.map(r => ({
      path: r.path,
      op: r.op,
      passed: r.passed,
      expected: r.expected,
      actual: r.actual,
    })),
  };
}
```

返回示例：

```json
{
  "passed": false,
  "summary": { "passed": 2, "total": 3 },
  "results": [
    { "path": "items.length", "op": "eq", "passed": true, "expected": 3, "actual": 3 },
    { "path": "total", "op": "gt", "passed": true, "expected": 0, "actual": 29.7 },
    { "path": "currentUser", "op": "exists", "passed": false, "expected": null, "actual": null }
  ]
}
```

---

### 强断言模式（`.expect()`，类 Jest 语义）

失败时抛出 `RSAssertionError`，适合 E2E 测试脚本：

```js
async () => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) throw new Error('未挂载');
  try {
    handle
      .expect('CartService_abc123')
      .toBe('items.length', 3)
      .toExist('currentUser')
      .expect(); // 失败时抛出 RSAssertionError
    return { passed: true };
  } catch (e) {
    return { passed: false, message: e.message };
  }
}
```

---

### 各类断言示例

#### 相等与不等

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  return handle
    .expect('UserService_xyz99')
    .toBe('loginState', 'logged_in')       // 精确相等
    .notToBe('errorCode', 403)             // 不等于
    .check();
}
```

#### 数值比较

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  return handle
    .expect('CartService_abc123')
    .toBeGreaterThan('total', 0)           // > 0
    .toBeGreaterThanOrEqual('items.length', 1)  // >= 1
    .toBeLessThan('items.length', 100)     // < 100
    .toBeBetween('total', 10, 500)         // 10 <= total <= 500
    .check();
}
```

#### 存在性检查

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  return handle
    .expect('UserService_xyz99')
    .toExist('currentUser')                // 不为 null/undefined
    .toExist('currentUser.token')          // 深层路径存在性
    .toNotExist('errorMessage')            // 为 null/undefined
    .check();
}
```

#### 数组与字符串包含

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  return handle
    .expect('ProductService_p001')
    .toInclude('tags', 'featured')         // 数组包含元素
    .toNotInclude('disabledFeatures', 'checkout')  // 数组不包含
    .toInclude('title', 'iPhone')          // 字符串包含子串
    .check();
}
```

#### 正则匹配

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  return handle
    .expect('UserService_xyz99')
    .toMatch('email', '^\\w+@\\w+\\.\\w+$')  // 正则匹配 email 格式
    .toMatch('phone', '^\\+?[0-9]{7,15}$')
    .check();
}
```

#### 类型检查

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  return handle
    .expect('CartService_abc123')
    .toBeType('total', 'number')           // typeof total === 'number'
    .toBeType('items', 'object')           // typeof items === 'object'（数组也是 object）
    .toBeType('isLoading', 'boolean')
    .check();
}
```

#### 长度断言

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  return handle
    .expect('CartService_abc123')
    .toHaveLength('items', 3)              // 精确长度
    .toHaveLengthGt('items', 0)            // 非空
    .toHaveLengthLte('items', 10)          // 不超过 10
    .check();
}
```

#### 对象键与子集匹配

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  return handle
    .expect('UserService_xyz99')
    .toHaveKeys('currentUser', ['id', 'name', 'email'])  // 包含所有指定 key
    .toMatchObject('currentUser', { role: 'admin', active: true })  // 浅层子集
    .toDeepEqual('config', { theme: 'dark', lang: 'zh' })  // 深比较
    .check();
}
```

#### 数组元素断言（some / every）

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  return handle
    .expect('CartService_abc123')
    // 至少一个商品价格 > 100
    .toHaveSome('items', { path: 'price', op: 'gt', expected: 100 })
    // 所有商品数量 >= 1
    .toHaveEvery('items', { path: 'quantity', op: 'gte', expected: 1 })
    .check();
}
```

#### 带自定义错误信息

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  return handle
    .expect('CartService_abc123')
    .toBe('items.length', 3, '购物车应有 3 个商品')
    .toExist('currentUser', '用户未登录')
    .check();
}
```

---

### 独立使用 `rsExpect(instance)`

脱离 `window` 挂载，直接传入 Service 实例，适合 E2E 测试框架或 Node.js 环境：

```js
// E2E 测试脚本中
import { rsExpect } from '@rabjs/devtools';

// 直接传 Service 实例
rsExpect(cartService, '购物车加购验证')
  .toBe('items.length', 1)
  .toBe('items.0.name', 'iPhone 15')
  .toBeGreaterThan('total', 0)
  .expect(); // 失败时抛出 RSAssertionError
```

在 evaluate_script 中组合使用（先取实例，再建立断言）：

```js
() => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return { error: '未挂载' };
  // 通过类名找到 Service 实例
  const entry = handle.listServices().find(s => s.identifierLabel === 'CartService');
  if (!entry) return { error: 'CartService 未找到' };
  // 直接对实例进行断言（等价于 handle.expect(instanceId)）
  const result = handle
    .expect(entry.instanceId)
    .describe('加购后状态验证')
    .toHaveLength('items', 1)
    .toBeGreaterThan('total', 0)
    .run();
  return { passed: result.passed, summary: result.summary };
}
```

---

### 断言 + 操作组合流程

先操作 Service，再断言状态变更：

```js
async () => {
  const handle = window.__RS_ROOT_CONTAINER__;
  if (!handle) return { error: '未挂载' };

  const entry = handle.listServices().find(s => s.identifierLabel === 'CartService');
  if (!entry) return { error: 'CartService 未找到' };

  // 1. 记录操作前状态
  const before = handle
    .expect(entry.instanceId)
    .toBe('items.length', 0)
    .run();

  // 2. 触发操作
  await entry.instance.addItem({ id: 'test-1', name: 'Test Product', price: 9.9 });

  // 3. 断言操作后状态
  const after = handle
    .expect(entry.instanceId)
    .toBe('items.length', 1)
    .toBeGreaterThan('total', 0)
    .run();

  return {
    before: { passed: before.passed, summary: before.summary },
    after: { passed: after.passed, summary: after.summary },
  };
}
```

---

## 常见问题

### `window.__RS_ROOT_CONTAINER__` 为 undefined？
- 页面未完成加载，稍等后重试

### `getService` 找不到 Service？

- 只有调用 `resolve` 后被实例化的 Singleton 才会出现
- 先用 `listServices()` 确认实例是否已存在及其 `instanceId`

### evaluate_script 返回值为 undefined / 不完整？

- 返回值必须是 JSON 可序列化的类型（基础类型、普通对象、数组）
- Service 实例不可直接返回，需提取其中的状态字段
- 避免返回含循环引用的对象
