AI 应用大模型AI Agent交互助手RAG【免费下载链接】obsidian-copilotRun agents in Obsidian - OpenCode, Codex, Claude Code etc.项目地址https://gitcode.com/gh_mirrors/ob/obsidian-copilot点击查看免费下载Obsidian Copilot 插件的完整测试体系实战指南。本文围绕仓库中 designdocs/agents/TESTING_GUIDE.md 展开覆盖单元测试设计工作流、基于 Obsidian 桌面版 CLI 的端到端测试、设置数据重置、组件画廊component gallery验证与性能快照采集。读完本文你将掌握一条从写行为描述 → 跑红绿灯测试 → 部署进真实 vault → 用 CLI 驱动并采集证据的完整可执行路径能够独立为涉及 React 树、真实 DOM 与设置持久化的改动建立可复现的验证闭环。测试金字塔什么时候该用哪一层仓库明确采用两层测试策略取舍标准只有一个——单元测试能否回答这个问题层级命令 / 手段适用场景单元测试npm run test纯逻辑、运行快、Mock 掉 Obsidian API。任何不直接触碰 Obsidian 自身的代码改动默认都写单元测试端到端E2EobsidianCLI 真实 vault需要真实 React 树、真实 Obsidian DOM 或真实设置持久化的场景——UI 回归、设置往返round-trip、插件生命周期、性能检查E2E 是最慢、最脆弱的一层只有当单元测试回答不了问题时才动用它。这一判断背后有明确的结构支撑仓库在 designdocs/agents/STYLE_GUIDE.md 中要求可测试代码——依赖注入、纯逻辑下沉到叶子模块、getSettings()等单例只允许出现在顶层编排处并给出了一个金标准litmus test能否用普通参数直接调用它完成测试如果答案是否说明这个依赖应该作为参数传入。按照这一结构写出来的代码天然大部分都能用纯单元测试覆盖E2E 只需补齐单元测试够不到的部分。单元测试可执行规格说明基础设施与运行方式Jest TypeScriptts-jestnpm run test运行整个单测套件单独跑一个用例用npm test -- -t test name。测试文件与被测实现放在同一目录命名*.test.ts/*.test.tsx。React 组件测试使用testing-library/react。测试环境的搭建细节可从 jest.config.js 与 jest.setup.js 中确认testEnvironment为jsdomroots覆盖src、dev、scripts三个目录moduleNameMapper将^obsidian$指向mocks/obsidian.js并把agentclientprotocol/sdk、anthropic-ai/claude-agent-sdk、react-resizable-panels等外部依赖替换为仓库内的桩实现jest.setup.js 为 jsdom 补齐了 Obsidian 运行环境TextEncoder/TextDecoder、matchMedia、Node.doc/Node.win增强、createEl/createDiv/createSpan等 DOM 创建助手、HTMLElement.setCssProps、addClass、setText以及activeDocument/activeWindow全局对象——这些 polyfill 意味着插件代码在 jsdom 下可以像在真实 Obsidian 里一样构建界面这正是Mock 掉 Obsidian API的具体落地方式。测试设计工作流单元测试是可执行的规格说明。一个开发者或 Agent 应该能仅凭describe/it的骨架就讲清楚模块做了什么、公开操作如何影响可观察状态或输出、以及它保持了哪些约束。套件全绿本身并不等于行为被正确覆盖。新增或修改行为时按以下五步执行先写行为大纲。从需求契约推导用例对每个受影响的可调用对象先建立其正常使用路径再补充边界、失败与回归用例。如果现有套件只覆盖了边界用例先补上缺失的正常行为。不要把实现分支照搬进测试也不要为不支持的假想状态发明用例。命名条件与结果。把每个describe加it读成一句完整的话说清楚什么变了 / 返回了什么 / 什么时候发生。避免works、handles updates、publishes once这类不解释行为的命名。必须保留的 issue URL 保留但要确保不看 issue 也能读懂描述。让测试体证明命名的主张。使用具体输入和有意义的 fixture 名称明确划分 arrange / act / assert 三部分。一个用例只承载一个行为场景可以用多个断言来确立该场景。断言可观察的结果——调用计数只在通知/交互属于契约的一部分时才有用不能代替检查承诺的状态或输出。助手函数用来消除搭建噪音但要让决定性输入和结果保持可见。红绿重构Red, Green, Refactor。在实现行为之前先跑新测试确认它是因为承诺的行为缺失或错误而失败而不是因为 setup、import 或 mock 坏了。做最小改动使其通过然后在不破坏绿色的前提下改进代码。为已工作行为补覆盖时先临时破坏该行为验证测试确实能发现再恢复。不要声称经历过没有实际观察到的红阶段。复查大纲与断言。只读测试名不看测试体新人能否借此讲清受影响模块的契约再逐个检查测试体如果命名的行为坏了它真的会失败吗修复覆盖或断言缺口只给弱测试改名是不够的。文档给出的示范是select()套件应该按去重之前的选择行为组织it(makes the chosen chat the context source for Relevant Notes, ...)it(switches the context source to another chat before notifying subscribers, ...)it(does not notify subscribers again when the same chat context object is reselected, ...)这套工作流践行的是测试先以规格说明的方式表达意图再让它们通过的原则源自 Robert C. Martin 的 Test First 主张。仓库中大量*.test.ts文件正是按这一模式组织的例如 src/agentMode/session 下的测试套件名称即可读作行为契约。E2E通过 Obsidian CLI 驱动真实 vaultE2E 是给编码 Agent 使用的田野指南用 Obsidian 桌面版 CLI 驱动 Copilot 插件。文档中的命令均基于 Obsidian1.12.7在真实 vault 中验证。CLI 位于/Applications/Obsidian.app/Contents/MacOS/obsidian——务必使用完整路径obsidianshim 不一定在PATH上。部署新构建到测试 vaultnpm run test:vault仅限 macOS。该命令安装依赖、构建并把当前工作树worktree的main.js/manifest.json/styles.css复制进$COPILOT_TEST_VAULT_PATH/.obsidian/plugins/copilot/然后通过 Obsidian CLI 重载插件。前提是$COPILOT_TEST_VAULT_PATH用户级环境变量指向一个至少被 Obsidian 打开过一次的 vault。这是让我的改动跑起来的规范步骤——不要手搓npm run build cp main.js …。实现细节见 scripts/test-vault.sh会拒绝在工作树位于目标 vault 内部时执行避免把源码变成 vault 内容、避免把插件部署到自身之上复制的 manifest 会带上开发版版本后缀并以可见标签携带 commit、clean/dirty 状态和 bundle 哈希[dev build: commit-state-hash | branch: branch | built: ts]重载采用 disable → enable 循环而不是plugin:reload——在该环境下plugin:reload返回成功但不会重新执行插件的onload新部署的main.js根本不会运行关键坑Obsidian CLI 的目标 vault 由当前工作目录决定它会解析 $PWD 所在的 vaultvault参数无法覆盖这一点因此脚本必须cd $VAULT_PATH后再调用 CLI否则重载会悄悄打到调用者 cwd 所在的 vault 上若多个 Conductor 工作树共用同一个测试 vault最后一次执行test:vault的那个生效——用下一节的 preflight 验证不要假设之后工作树的再次构建不会改动已部署的插件需要重新执行test:vault才会部署并重载新构建。重置测试 vault 的设置npm run test:reset-data # 使用内置的 clean-onboarding fixture npm run test:reset-data -- path # 使用你指定的任意 data.json它是test:vault的搭档用 fixture 覆盖$COPILOT_TEST_VAULT_PATH/.obsidian/plugins/copilot/data.json然后重载插件与test:vault相同的 Obsidian CLI 重载使重置立即生效——重载很重要因为运行中的插件在内存中持有设置否则会在下次保存时把文件覆盖回去。实现见 scripts/test-reset-data.sh在触碰目标之前会先用 Node 校验源文件是合法 JSON防止损坏的 fixture 污染 vault。无参数时使用 scripts/test-fixtures/data.clean-onboarding.json不配置任何编码 Agent、也没有 BYOK 模型providers/configuredModels/backends均为空用于从零开始测试 Agent 引导流程。该 fixture 刻意省略_keychainOnly因此插件以磁盘模式加载忽略 OS 钥匙串中遗留的任何 API key——一个确定性的干净状态。data.legacy-byok.json——一次性 BYOK 迁移npm run test:reset-data -- scripts/test-fixtures/data.legacy-byok.json加载一个版本前的旧安装无settingsVersion旧的activeModels 顶层 provider key新的数据切片为空仅预置了一个用于验证去重的 BYOK Anthropic provider。下次插件加载时runSettingsMigrations会把旧版 BYOK providers/models 转换为新的providers/configuredModels/backends形状使其能在 OpenCode 中工作并盖上settingsVersion印章。它以磁盘模式运行使用明显的假sk-test-…key。迁移后的验证命令$OBS vault$VAULT eval codeapp.plugins.plugins.copilot.loadData().then(dJSON.stringify({ settingsVersion: d.settingsVersion, providers: Object.values(d.providers).map(p({type:p.providerType, origin:p.origin.kind, catalog:p.origin.catalogProviderId, hasKey: p.apiKeyKeychainId!null})), configuredModels: d.configuredModels.map(mm.info.id), chat: d.backends.chat?.enabledModels?.length, opencode: d.backends.opencode?.enabledModels?.length, legacyUntouched: { anthropicApiKey: d.anthropicApiKey, activeModels: d.activeModels.length } }))预期结果settingsVersion已盖章可路由的 provider 带有catalogProviderId和 keybackends.opencode.enabledModels非空且排除了 embeddingBAAI/bge-m3与 Bedrock 行恰好一个 Anthropic BYOK provider预置的那个——其描述符被去重而非重建旧版anthropicApiKey与activeModels保持原样。第二次重载不得改变settingsVersion或产生重复版本门控生效。这一行为在源码中有完整印证src/settings/migrations/index.ts 的runSettingsMigrations是逐版本门控的顺序执行器——只有fromVersion 4的 vault 才跑 BYOK 迁移最终无条件把settingsVersion升到CURRENT_SETTINGS_VERSION即使某个 provider 迁移失败也不会卡住门控反复重跑。0. 金律先选对窗口Obsidian 是单个 Electron 应用每个打开的 vault 有一个 renderer。CLI 一次只与一个renderer 通信默认目标是最近被触碰的 vault焦点 上次 CLI 目标——跨调用不稳定。开发机上同时开着多个 vault 时几乎必然出现本地明明好好的这种假象。每条命令都必须带vaultname。该 flag 是全局的对所有子命令生效把调用绑定到特定 rendererOBS/Applications/Obsidian.app/Contents/MacOS/obsidian VAULT$(basename $COPILOT_TEST_VAULT_PATH) $OBS vault$VAULT vault # 回显目标 vault 的名称/路径任何测试运行前的验证门运行 preflight有任何异常立即中止$OBS vault$VAULT eval codeJSON.stringify({ vault: app.vault.getName(), path: app.vault.adapter.basePath, copilotLoaded: !!app.plugins.plugins.copilot, copilotVersion: app.plugins.manifests.copilot?.version, buildTag: app.plugins.manifests.copilot?.description?.match(/dev build: ([^ |])/)?.[1], buildBranch: app.plugins.manifests.copilot?.description?.match(/branch: ([^ |])/)?.[1] })检查项vault与预期目标一致copilotLoaded为truevaultname会静默地打开一个已知但关闭的 vault 作为新 renderer插件可能还没就绪若为false通过eval调用app.plugins.loadPlugin(copilot)再重新探测buildTag以当前工作树的短 commit 开头、报告预期的 clean/dirty 状态且在下次部署前保持不变buildBranch与你实际构建的分支一致——多个 Conductor 工作区共享一个测试 vault 时最后运行npm run test:vault的胜出——验证不要假设。需要识别的失败模式症状原因修复Vault not found.vaultname拼写错误区分大小写匹配vaults verbose显示的 vault 名运行$OBS vaults verbose列出已知 vault命令以 0 退出但 stdout 为空eval无返回vault 已注册但 renderer 尚未打开——Obsidian 正在启动它sleep 2 retry然后验证默认 vault 在命令之间反复变化你漏了vaultname——焦点移动了始终传 flag绝不依赖默认值截图显示的是另一个 vault同上——捕获的是焦点 renderer 而非目标 renderer显式带vaultname重拍1. 每次会话只设置一次$OBS dev:debug on # 附加 Chrome DevTools 协议dev:console、dev:errors、dev:cdp 的前置条件 $OBS vault$VAULT eval codeapp.plugins.loadPlugin(copilot) # 若未自动加载dev:debug on在 Obsidian 进程生命周期内是粘性的重复运行是 no-op但 Obsidian 重启后必须重新执行否则dev:console、dev:errors、dev:cdp都不会返回有用结果。2. 驱动插件运行命令$OBS vault$VAULT command idcopilot:agent-chat-open-window完整命令 ID 清单$OBS vault$VAULT eval codeJSON.stringify(Object.keys(app.commands.commands).filter(cc.startsWith(copilot)))注意顶层obsidian commands只列出 Obsidian 核心命令插件命令只能通过app.commands.commands看到。编程方式读写设置设置既存在于磁盘上的data.json也存在于运行时的 Jotai atom store。测试最快速、最确定性的路径是绕过 UI# 读 $OBS vault$VAULT eval codeapp.plugins.plugins.copilot.loadData().then(dJSON.stringify({temperature:d.temperature,defaultChainType:d.defaultChainType})) # 写并持久化 $OBS vault$VAULT eval code(async(){ const papp.plugins.plugins.copilot; const dawait p.loadData(); d.temperature0.42; await p.saveData(d); return (await p.loadData()).temperature; })()GotchasaveData()会写入磁盘但不会推入已打开设置弹窗的 React 状态。如果需要 UI 反映改动请关闭并重新打开设置command idapp:open-settings或者干脆通过 UI 本身驱动见第 4 节。3. 捕获证据截图——dev:screenshot$OBS vault$VAULT dev:screenshot path/tmp/before.png sleep 1 # 文件是异步写入的CLI 返回时可能还没落盘然后用 Read 工具读取 PNG 做视觉检查。路径可以是绝对路径/tmp/foo.png或 vault 相对路径。永远搭配vaultnameflag——否则可能捕获到处于焦点的那个 Obsidian 窗口。一个有用的 sanity check裁切/检查底部状态栏它会显示 vault 名。DOM 查询——dev:dom只读默认返回outerHTML也可返回文本、属性或计算样式$OBS vault$VAULT dev:dom selector.vertical-tab-nav-item.is-active .vertical-tab-nav-item-title text $OBS vault$VAULT dev:dom selector.modal.mod-settings input[typetext] all $OBS vault$VAULT dev:dom selector.copilot-chat-input attrplaceholder复杂检查循环、结构化数据优先用evaldocument.querySelectorAll——dev:dom输出很快会变得冗长且 CLI 是同步流式返回的。控制台与错误$OBS vault$VAULT dev:console limit30 $OBS vault$VAULT dev:console levelerror limit20 $OBS vault$VAULT dev:errors clear # 场景开始前重置 $OBS vault$VAULT dev:errors # 场景结束后采集dev:errors只捕获未捕获的错误。测试模式clear → 跑场景 → 等待 → 重新读取。计数大于零意味着回归。性能快照$OBS vault$VAULT dev:cdp methodPerformance.enable $OBS vault$VAULT dev:cdp methodPerformance.getMetrics # JSHeap*、Nodes、JSEventListeners、ScriptDuration 等 $OBS vault$VAULT dev:cdp methodMemory.getDOMCounters # { documents, jsEventListeners, nodes } $OBS vault$VAULT eval codeJSON.stringify(performance.memory)动作计时两次 RAF 强制 layout paint 提交$OBS vault$VAULT eval code(async(){ const t0performance.now(); document.querySelector([data-setting-id\copilot\]).click(); await new Promise(rrequestAnimationFrame(()requestAnimationFrame(r))); return performance.now()-t0; })()泄漏检测的 diff 模式快照Memory.getDOMCounters→ 重复执行该功能 N 次 → 再快照。若nodes或jsEventListeners持续攀升超过 N 次迭代说明该功能在泄漏。4. 与 UI 交互点击、输入CLI 没有一等公民的点击选择器或向输入框打字命令两条真实可行的路线4a.eval——合成 JS 事件快、容易、略假对大多数插件 UI 有效# 点按钮 $OBS vault$VAULT eval codedocument.querySelector(.send-button)?.click() # 给 React 受控输入赋值 $OBS vault$VAULT eval code const el document.querySelector(.copilot-chat-input); const setter Object.getOwnPropertyDescriptor(el.constructor.prototype,value).set; setter.call(el, hello); el.dispatchEvent(new Event(input,{bubbles:true})); 注意事项合成事件的isTrustedfalse任何以此为门槛的逻辑都不会触发Lexical / contentEditable 表面通常需要beforeinput而不是input。4b.dev:cdp——真实的浏览器级输入慢、忠实当合成事件被拒绝、需要isTrustedtrue或者要验证 UI 本身行为正确focus、校验器、onChange、modal时使用# 先取目标坐标 $OBS vault$VAULT eval code const el document.querySelector([data-setting-id\copilot\]); const r el.getBoundingClientRect(); JSON.stringify({x:Math.round(r.xr.width/2), y:Math.round(r.yr.height/2)}) # 真实点击 $OBS vault$VAULT dev:cdp methodInput.dispatchMouseEvent params{type:mousePressed,x:174,y:764,button:left,clickCount:1} $OBS vault$VAULT dev:cdp methodInput.dispatchMouseEvent params{type:mouseReleased,x:174,y:764,button:left,clickCount:1} # 聚焦输入框再通过浏览器输入层打字 $OBS vault$VAULT eval codedocument.querySelector(.copilot-chat-input).focus() $OBS vault$VAULT dev:cdp methodInput.insertText params{text:hello from cdp} $OBS vault$VAULT dev:cdp methodInput.dispatchKeyEvent params{type:keyDown,key:Enter,code:Enter,windowsVirtualKeyCode:13}已验证端到端CDP 的Input.insertText写入设置字段会沿 React onChange 路径传播并持久化到data.json。设置弹窗的 React 状态保持同步与saveData路径不同。4c. Popout 窗口——第二个定位陷阱即使vaultname把你带到正确的 rendererObsidian 还支持popout 窗口把视图拆到独立 Electron 窗口。CLI 的附加点只能是主 vault renderereval的document/window永远指向主窗口dev:dom和dev:cdp Input.*操作的是主窗口的 document 和输入层dev:screenshot只捕获主 vault 窗口——popout 不在画面里。要从 CLI 触达 popout遍历app.workspace.floatingSplit$OBS vault$VAULT eval codeJSON.stringify({ popouts: app.workspace.floatingSplit?.children?.length || 0, popoutTypes: (app.workspace.floatingSplit?.children || []).map(cc.children?.[0]?.children?.[0]?.view?.getViewType?.()) })查询 popout 的 DOM 使用 Obsidian 附加到每个节点上的.win/.doc属性$OBS vault$VAULT eval code const popout app.workspace.floatingSplit.children[0]; const doc popout.win.document; doc.querySelector(.copilot-chat-input)?.value 对 popout 做 UI 输入时优先用针对popout.win.document.querySelector(...)的eval合成事件。CDP 层Input.*事件只去主窗口到不了 popout——当前 CLI 表面无法绕开。如果你的测试场景依赖 popout 交互便宜的修复是不把视图弹出去全部在主侧栏 leaf 中驱动。相关运行时陷阱插件代码而非测试代码标准的instanceof检查跨 realm 会失败——popout 窗口有自己独立的HTMLElement、MouseEvent等构造函数。在eval内部需要跨 realm 类型检查时用element.instanceOf(HTMLElement)/event.instanceOf(MouseEvent)。5. Vault 数据准备要获得确定性测试 fixture优先通过 CLI 创建笔记而不是手改 vault$OBS vault$VAULT create nametest-note content# Hello\n\nbody $OBS vault$VAULT append filetest-note content\nmore text $OBS vault$VAULT delete filetest-note permanent $OBS vault$VAULT search querycopilot formatjson limit10更激进的 reset 可以直接操作 vault 路径下的磁盘文件路径来自app.vault.adapter.basePath然后调用$OBS vault$VAULT reload让 Obsidian 重新扫描。组件画廊工作流当功能新增或修改用户可见的 React 组件或重要的视觉状态时使用组件画廊。非视觉的后端、数据或工具类改动不要求 story。画廊验证是对可调用级单元覆盖的补充不能替代它。代表性的设置、打开菜单、权限 diff 与过渡状态检查清单及剩余缺口见 designdocs/GALLERY_REVIEW_COVERAGE.md。编写 story在组件旁新增或更新*.stories.tsx文件。生成的画廊索引自动发现src/**/*.stories.tsx不要编辑dev/gallery/stories.generated.ts。从/lib/story导入Meta和StoryObj用satisfies MetaProps声明组件元数据每个具名 story 都标注StoryObjProps。该模块的契约见 src/lib/story.tsHost leaf | modal | popover | settings-tabLayout padded | centered | fullscreen另支持coverage与modalClass参数。覆盖用户真正能看到的承重状态default、empty、loading、success、error、disabled、容易溢出的内容以及该功能引入的其他状态。使用写实的文案与 fixture props而不是生产 store 或运行时单例。普通 prop 状态优先用args组合场景用render。支持 hook 驱动的 render 函数。保持 fixture 确定性、action 惰性——除非交互本身就是被测行为。用parameters.gallery.hostleaf、modal、popover或settings-tab与layoutpadded、centered、fullscreen匹配真实组件边界。只有有意的纯展示组件豁免才用coverage: false。story 不能钉死画布宽度——宽度是画廊宽度工具栏持有的视图状态所以要用该工具栏在其他宽度检查组件或用audit()一次扫完全部宽度。永远不要添加一个与兄弟 story 唯一区别是你希望它渲染的宽度的 story它会渲染得一模一样。如果组件离开插件状态无法渲染抽出或暴露一个接受所需数据作为 props 的展示边界不要为了触达设置、store 或运行时单例而扩大画廊的 import 围栏。一个最小的相邻 story 长这样import type { Meta, StoryObj } from /lib/story; import { StatusCard, type StatusCardProps } from ./StatusCard; const meta { title: Feature/Status Card, component: StatusCard, parameters: { gallery: { host: leaf, layout: padded } }, } satisfies MetaStatusCardProps; export default meta; export const Error: StoryObjStatusCardProps { args: { message: The operation could not be completed., tone: error }, };进入真实验证前先运行改动组件与 story 契约的聚焦单元测试然后构建开发插件npm run gallery:build通过npm run gallery:vault部署到COPILOT_TEST_VAULT_PATH配置的非生产测试 vault。任何 CLI 或 UI 变更之前先验证解析出的 vault 名称和路径与该配置一致永远不要依赖隐式焦点 renderer。画廊与插件共享样式表画廊的样式表通过把 src/styles/tailwind.css 拼接到自己的源码构建而来因此携带几乎完整的生产样式表副本——而 Obsidian 会把每个已启用插件的styles.css全文档注入。两份副本以相同特异性进入同一级联所以一份基于旧src/styles/tailwind.css构建的画廊副本会压过已部署的生产规则让插件自己的视图渲染出改动前的行为。npm run test:vault保持二者同步只要 vault 安装了画廊插件它就会从当前部署的工作树重建画廊并重新链接、重载。gallery:vault把整个源码目录符号链接进去所以这也修复了因工作树被删除而悬空的链接如果 vault 没有画廊插件这步会被跳过。如果某个 CSS 改动看起来没生效先在 inspector 里检查同一选择器是否出现两次再怀疑改动本身。故事树保持顶层分类展开。选中分类标签显示其 contact sheet对嵌套分类标签同时折叠/展开子树——只想切换选中的 contact sheet 时用旁边的 chevron。切换不同 story 或 contact sheet 不会关闭你已经展开的分支。Agent 验证循环当开发专用组件画廊插件加载后其类型化的window.__gallery句柄可以不依赖截图地选择并审计 story。句柄契约见 dev/gallery/main.tslist(): string[]、show(id, options): Promisevoid、audit(options): PromiseAuditReport[]。短异步调用可以同时设置awaitPromise与returnByValue。完整审计时把最终结果存进页面并轮询——即使设置了awaitPromiseObsidian CLI 也可能在长 CDP promise 落定前就返回。# 发现精确的 story id $OBS vault$VAULT dev:cdp methodRuntime.evaluate params{expression:window.__gallery.list(),returnByValue:true} # 渲染一个 story等待其副作用与布局落定 $OBS vault$VAULT dev:cdp methodRuntime.evaluate params{expression:window.__gallery.show(\UI/Button/Variants\,{width:300}).then(()true),awaitPromise:true,returnByValue:true} # 用稳定身份 请求宽度定位已挂载的用例 $OBS vault$VAULT dev:dom selector[data-storyUI/Button/Variants][data-story-width300] all # 启动精确宽度扫描不依赖 CLI 连接去 await 它 $OBS vault$VAULT dev:cdp methodRuntime.evaluate params{expression:window.__galleryRun{status:\pending\};window.__gallery.audit({widths:[300,340,400,600]}).then(value{window.__galleryRun{status:\fulfilled\,value}},reason{window.__galleryRun{status:\rejected\,reason:String(reason?.stack??reason)}});true,returnByValue:true} # 反复读取直到 fulfilled 或 rejected。fulfilled 值每个宽度一个 AuditReport $OBS vault$VAULT dev:cdp methodRuntime.evaluate params{expression:window.__galleryRun,returnByValue:true}audit()返回形如[{theme,width,findings:[{story,check,detail}]}]的数组接口定义见 dev/gallery/audit.ts审计检查类型包括contrast、off-token-color、overflow、render-failure、unsupported-color、zero-size。请求的宽度可以是任意正有限像素值上面四个值与画廊可见按钮对应。通过该句柄theme 是刻意只读的切换 Obsidian 外观设置再跑一次相同的宽度数组扫描。完整的自动化通过检查最后还要单独核对未捕获错误$OBS vault$VAULT dev:errors clear # 运行上面的 store-and-poll 扫描并等待 fulfilled/rejected 状态 $OBS vault$VAULT dev:errors典型 E2E 流程最小冒烟测试脚手架OBS/Applications/Obsidian.app/Contents/MacOS/obsidian VAULT$(basename $COPILOT_TEST_VAULT_PATH) # 1. Preflight——验证目标 构建 $OBS vault$VAULT eval codeJSON.stringify({ v: app.vault.getName(), loaded: !!app.plugins.plugins.copilot, ver: app.plugins.manifests.copilot?.version }) || exit 1 # 2. 附加调试器清空错误缓冲区 $OBS vault$VAULT dev:debug on $OBS vault$VAULT dev:errors clear # 3. 基线截图 指标 $OBS vault$VAULT dev:screenshot path/tmp/test-before.png $OBS vault$VAULT dev:cdp methodMemory.getDOMCounters /tmp/dom-before.json # 4. 跑场景 $OBS vault$VAULT command idcopilot:agent-chat-open-window sleep 1 $OBS vault$VAULT eval codedocument.querySelector(.copilot-chat-input)?.focus() $OBS vault$VAULT dev:cdp methodInput.insertText params{text:test prompt} # 5. 采集证据 sleep 2 $OBS vault$VAULT dev:screenshot path/tmp/test-after.png $OBS vault$VAULT dev:cdp methodMemory.getDOMCounters /tmp/dom-after.json $OBS vault$VAULT dev:console levelerror limit20 /tmp/console-errors.txt $OBS vault$VAULT dev:errors /tmp/uncaught.txt # 6. 断言错误为空、截图看起来正确、节点数没有爆掉快速参考需求命令列出打开的 vaultvaults verbose指定目标 vault每条命令加前缀vaultname验证目标 vault 正确vaultname eval codeapp.vault.getName()视觉确认vaultname dev:screenshot path/tmp/x.png读取前 sleep ≥1s未加载时加载插件vaultname eval codeapp.plugins.loadPlugin(copilot)列出插件命令vaultname eval codeJSON.stringify(Object.keys(app.commands.commands).filter(cc.startsWith(copilot)))运行插件命令vaultname command idcopilot:id读设置磁盘vaultname eval codeapp.plugins.plugins.copilot.loadData().then(JSON.stringify)改设置磁盘vaultname eval code(async(){const papp.plugins.plugins.copilot;const dawait p.loadData();d.XY;await p.saveData(d);})()用 JS 点击vaultname eval codedocument.querySelector(...).click()用 CDP 点击dev:cdp methodInput.dispatchMouseEvent params{type:mousePressed,...}用 CDP 输入dev:cdp methodInput.insertText params{text:...}未捕获错误dev:errorsclear run read控制台消息dev:console levelerror limit20DOM 规模dev:cdp methodMemory.getDOMCounters堆内存eval codeJSON.stringify(performance.memory)完整性能指标dev:cdp methodPerformance.getMetrics先Performance.enable落地建议把这套流程固化成每次改动的默认动作先在 designdocs/agents/STYLE_GUIDE.md 的可测试结构指导下写可调用级单元测试覆盖正常路径 边界 回归命名读作契约纯逻辑问题就地解决只有需要真实 React 树、真实 DOM 或设置持久化时才走npm run test:vault部署 带vaultname的 CLI 驱动 截图/控制台/DOM 计数器证据采集的 E2E 通道涉及用户可见组件时再叠加画廊 story 与window.__gallery.audit()宽度扫描。三条通道互补共同构成对 designdocs/agents/TESTING_GUIDE.md 所述单元测试优先、E2E 兜底、画廊补视觉分层策略的完整执行。赞分享AI 应用大模型AI Agent交互助手RAG【免费下载链接】obsidian-copilotRun agents in Obsidian - OpenCode, Codex, Claude Code etc.项目地址https://gitcode.com/gh_mirrors/ob/obsidian-copilot点击查看免费下载相关推荐cli-anything-obsidian 测试计划解读Mock 单元测试与真实 Obsidian 端到端验证实战指南cli anything obsidian 测试计划解读Mock 单元测试与真实 Obsidian 端到端验证实战指南 导读 cli anything obs人工智能AI AgentAI 技能工具调用CLICANN/asc-devkit BitwiseXor临时缓冲区因子大小GetBitwiseXorTmpBufferFactorSizea nameZH CN_TOPIC_0000002441333404 /a 功能说明人工智能深度学习算子库CANNAscendBentoML 服务测试指南从单元测试到端到端验证的完整实战BentoML 服务测试指南从单元测试到端到端验证的完整实战 本文以 BentoML 官方文档《Test API endpoints》为核心骨架系统讲解如何模型推理服务人工智能后端大模型MLOpsLLMOps上一篇5 分钟上手 Starship 配置从安装到换主题的完整指南下一篇discord.py 终极入门10分钟打造你的第一个 Discord BotPython新手友好创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
