agentsclimarketplace

Rab cdp debug

Skill ximing/rab/skills/rab-cdp-debug

用于指导 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。From its SKILL.md

Install
npx -y skills add ximing/rab --skill rab-cdp-debug

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 11 stars11 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

18.4 KB, as published. Nobody here has run it

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. 安装依赖:
pnpm add @rabjs/devtools
  1. 在应用入口(如 main.tsx)调用一次:
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 接口

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 是否已挂载

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

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

() => {
  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,
  }));
};

返回示例:

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

3. 通过 instanceId 获取 Service 状态

先通过 listServices 获得 instanceId,再读取 Service 内部状态:

() => {
  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 时,先通过类名找到:

() => {
  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 方法并验证结果

() => {
  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 方法

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

() => {
  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 状态(验证后清理)

() => {
  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 存在

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

Step 2:枚举所有 Service,找到目标

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

Step 3:取出 Service 当前状态快照

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

Step 4:触发操作

() => {
  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:验证状态变更

() => {
  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 === expectedeq
.notToBe(path, expected)actual !== expectedneq
.toBeGreaterThan(path, n)actual > ngt
.toBeGreaterThanOrEqual(path, n)actual >= ngte
.toBeLessThan(path, n)actual < nlt
.toBeLessThanOrEqual(path, n)actual <= nlte
.toBeBetween(path, lo, hi)lo <= actual <= hibetween
.toExist(path)actual != nullexists
.toNotExist(path)actual == nullnotExists
.toInclude(path, item)数组/字符串包含includes
.toNotInclude(path, item)数组/字符串不包含notIncludes
.toMatch(path, pattern)正则匹配matches
.toBeType(path, type)typeof actual === typetype
.toHaveLength(path, n)actual.length === nlength
.toHaveLengthGt(path, n)actual.length > nlengthGt
.toHaveLengthGte(path, n)actual.length >= nlengthGte
.toHaveLengthLt(path, n)actual.length < nlengthLt
.toHaveLengthLte(path, n)actual.length <= nlengthLte
.toHaveKeys(path, keys)对象包含所有指定 keyhasKeys
.toMatchObject(path, subset)对象浅层匹配子集matchObject
.toDeepEqual(path, expected)JSON 深比较deepEq
.toHaveSome(path, assertion)数组中至少一项满足some
.toHaveEvery(path, assertion)数组所有项满足every

基本示例:控制台调试(.check())

控制台快速验证,带彩色报告输出,返回 boolean:

() => {
  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 跨进程传递:

() => {
  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,
    })),
  };
};

返回示例:

{
  "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 测试脚本:

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 };
  }
};

各类断言示例

相等与不等

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

数值比较

() => {
  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();
};

存在性检查

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

数组与字符串包含

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

正则匹配

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

类型检查

() => {
  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();
};

长度断言

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

对象键与子集匹配

() => {
  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)

() => {
  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()
  );
};

带自定义错误信息

() => {
  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 环境:

// 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 中组合使用(先取实例,再建立断言):

() => {
  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,再断言状态变更:

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 实例不可直接返回,需提取其中的状态字段
  • 避免返回含循环引用的对象

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.