ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

在 RStudio 中驱动 panmirror 可视化编辑器:基于 Playwright 的引文对话框与插入菜单自动化测试模式

在 RStudio 中驱动 panmirror 可视化编辑器:基于 Playwright 的引文对话框与插入菜单自动化测试模式 开发工具后端【免费下载链接】rstudioRStudio is an integrated development environment (IDE) for R项目地址https://gitcode.com/gh_mirrors/rs/rstudio点击查看免费下载本篇文章聚焦 RStudio 仓库中.claude/skills/rstudio-create-playwright-tests/visual-editor.md所沉淀的视觉WYSIWYGMarkdown 编辑器测试模式系统讲解如何用 Playwright 驱动 panmirror 可视化编辑器中的 Insert Citation 对话框、Insert/Format 菜单等 React UI。读完本文你将掌握进入可视化模式的可靠姿势、React 渲染与 GWT 对话框的 DOM 层级差异、react-window 虚拟列表与一次性初始化重置的规避技巧、latent search 的真实触发方式以及针对外部引文服务故障的跳过而非失败断言策略——这些模式在 citations.test.ts 等测试中已经过实战验证。背景为什么可视化编辑器需要一套独立的测试模式RStudio 的编辑器提供两种 Markdown 编辑模式源代码source模式基于 Ace 编辑器而可视化visual模式则是所见即所得WYSIWYG的panmirror。panmirror 是一个 React 应用其源码位于 Quarto 仓库packages/editor/src/behaviors/...不在当前 RStudio 仓库内RStudio 侧只能通过 DOM 与 RPC 边界与它交互。由此产生两个直接后果功能隔离引文Citations与 Insert 菜单仅存在于可视化模式中源代码模式下根本不可见DOM 结构差异panmirror 的 UI 由 React 渲染其层级与 GWT 渲染的对话框外壳完全不同沿用 GWT 时代的对话框作用域选择器会定位不到任何真实控件。因此所有与可视化编辑器交互的 Playwright 测试都必须遵循本文档沉淀的统一模式而不是把源模式的选择器习惯生搬硬套。进入可视化模式ensureVisualMode()的正确用法驱动引文功能的第一步是让编辑器进入可视化模式。推荐统一调用SourcePaneActions.ensureVisualMode()它在 source_pane.actions.ts 中的实现体现了三个关键设计幂等探测先检查.ProseMirror是否已经挂载可视化模式下编辑器会挂载 ProseMirror 表面已处于可视化模式则直接返回等待工具栏就绪等待rstudio_visual_md_on切换开关定义于 source_pane.page.ts可见超时 5 秒内不出现则视为该文件类型不支持可视化模式并静默返回处理首次切换的 pandoc 转换确认对话框首次将文档切换到可视化模式时pandoc 转换是服务端往返可能触发一个确认对话框。实现不依赖固定轮询窗口而是同时等待「对话框出现」或「编辑器挂载」二者之一proseMirror.or(okBtn)超时 90 秒对话框出现则点击#rstudio_dlg_ok确认随后再等待.ProseMirror真正挂载。宿主的选取测试中新建文档时newMarkdownDoc命令通过executeCommand触发是最廉价的选择——它不需要rmarkdown包也不会弹出 Quarto 新建文档向导。这一点在 citations.test.ts 的newMarkdownVisualDoc辅助函数中有明确注释配合installDepIfPrompted处理可能出现的依赖安装提示再用selectedTab断言确认未命名文档已打开后进入可视化模式。panmirror 渲染在 GWT 对话框之外选择器作用域是page而非dialog这是最容易踩坑的一点panmirror 的 UI例如 Insert Citation 面板渲染在一个 React 根节点中这个根节点是 GWT[roledialog]外壳的兄弟节点而不是它的子节点。以 Insert Citation 对话框为例insert_citation.page.ts 的注释明确指出导航树、搜索框、结果列表等 React 控件都是page 作用域page.locator(...)只有 GWT 的底部按钮Insert / Cancel位于[roledialog]内通过this.dialog.getByRole(button, { name: Insert })定位。如果习惯性地把选择器限定在对话框作用域内例如locator(button)或对整个对话框做outerHTML导出得到的将是空结果或错误的 DOM 快照——真实控件根本不在那个子树里。规则可视化编辑器相关的所有 React 控件一律使用page级选择器。Insert Citation 对话框打开、选中与一次性初始化重置打开对话框在可视化模式下通过 Insert 菜单打开await page.locator([aria-labelInsert]).click(); await page.locator(#rstudio_label_citation_command).click(); await page.locator([roledialog][aria-labelInsert Citation]) .waitFor({ state: visible, timeout: 15000 });选中数据源点节点行不点图标导航树中的每个数据源节点以.pm-navigation-tree-node呈现内部含有一个img[altSource]例如 DataCite、Crossref、PubMed、From DOI。必须点击节点行本身而不是图标或其包裹层——点击图片或外层只移动高亮不会真正选中节点const node page.locator(.pm-navigation-tree-node, { has: page.locator([altDataCite]), });此外该导航树由react-window 虚拟化选中后列表会滚动因此不要对树做快速连点。高亮 ≠ 选中.pm-selected-navigation-tree-item高亮类移动到某个源上不代表该源的搜索结果面板已经激活。验证真正选中必须观察源自身的证据例如搜索框的出现占位符如Search DataCite for Citations或 Search 按钮变为可见。绝不能以高亮类作为选中判据。熬过一次性的初始化重置这是 Insert Citation 对话框最微妙的时序问题当对话框的参考文献bibliography加载完成时加载后的配置轮询会触发一次setSelectedPanelProvider把活动面板弹回对话框初始选中的节点新开对话框时即 My Sources同时树的高亮位置保持不变。这个重置最多只触发一次。因此正确的做法是选中若面板被重置回退则再选一次——第二次点击发生在重置之后是永久生效的。来自文档的完整防护代码const node page.locator(.pm-navigation-tree-node, { has: page.locator([altDataCite]) }); const box page.getByPlaceholder(Search DataCite for Citations); await node.click(); await expect(box).toBeVisible({ timeout: 15000 }); const deadline Date.now() 6000; while (Date.now() deadline) { if (!(await box.isVisible().catch(() false))) { await node.click(); // reverted; re-select (now permanent) await expect(box).toBeVisible({ timeout: 15000 }); break; } await page.waitForTimeout(200); }这段逻辑在产品代码中已经被封装为 insert_citation.page.ts 的selectSource()它先用expect(...).toPass({ timeout: 30000 })把「点击节点 → 断言搜索框可见」作为一个可重试单元重置若在面板首次可见前触发普通可见性等待只会超时再用一个 6 秒窗口监控面板是否回退并重新点击。R Package 源typeahead 面板无 Search 按钮占位符与 My Sources 共享则改用「监视该包自身结果行是否消失」来探测重置见selectPackageSource()第 127-160 行。Latent search输入不触发搜索只有 Enter / 粘贴 / Search 按钮才触发四个网络型引文源DataCite、Crossref、From DOI、PubMed使用的都是latent search面板从 Quarto 编辑器源码确认四个源渲染同一面板仅树标签与占位符不同。键入只更新搜索词不会发起搜索搜索在以下时机触发按 Enter粘贴内容点击 Search 按钮button.pm-insert-citation-panel-latent-search-button。Search 按钮只要源面板处于激活态就存在仅在搜索进行中时禁用。标准流程是「键入 → 点击按钮 → 轮询结果」await box.pressSequentially(bobolink); // real keystrokes, not fill() await page.locator(button.pm-insert-citation-panel-latent-search-button).click(); const results page.locator(.pm-insert-citation-source-panel-item-detailed); await expect.poll(() results.count(), { timeout: 30000 }).toBeGreaterThan(0);务必用pressSequentially不要用fill()fill()只设置 DOM 值不会可靠地触发 React 受控输入的onChange结果就是输入框里明明显示着文字但内部搜索词仍为空Enter 或 Search 按钮点击后毫无反应。InsertCitationDialog.search()insert_citation.page.ts封装的正是这一序列R Package 的 typeahead 面板则每键入一个字符就实时过滤无 Search 按钮清空残留输入同样使用ControlOrMetaaBackspacepressSequentially的真实按键序列。引文搜索 RPC 是异步的浏览器 ↔ rsession 的 RPC 缝datacite_search、crossref_works、pubmed_search、doi_fetch_csl都是异步 RPCPOST 请求只立即返回{asyncHandle}真正的结果稍后通过/events/get_events以键控于该 handle 的async_completion事件送达。服务端注册代码在仓库的 panmirror 模块中逐一可见SessionPanmirrorDataCite.cppregisterAsyncRpcMethod(datacite_search, dataCiteSearch)SessionPanmirrorCrossref.cppcrossref_worksSessionPanmirrorPubMed.cpppubmed_searchSessionPanmirrorDOI.cppdoi_fetch_csl。以 DataCite 为例dataCiteSearch从请求参数读取query向 DataCite 的dois端点发起服务端 HTTP 请求再把 JSON:API 响应转换为记录数组通过 continuation 异步返回SessionPanmirrorDataCite.cpp。这对测试的直接影响是架构性的服务端的 HTTP 请求发生在 rsession 进程内Playwright 无法在 API 边界拦截或模拟它——network.ts 明确注释了这一点请求源自 C session 进程而非浏览器因此不能用 Playwright 路由 mock。Playwright 唯一可及的是浏览器 ↔ rsession 的 RPC 缝。所以对这类搜索测试只能依赖真实服务响应并通过「跳过不可达服务」来隔离环境问题详见下一节。搜索失败的判定策略服务错误可跳过无结果无错误必须失败引文搜索在rsession中执行因此 Node 侧的可达性探测通过并不代表查询一定成功——例如 NCBI 会对共享的 CI 出口 IP 限流。这是服务问题不是产品缺陷应当跳过skip而非失败fail但另一方面超时且既无结果也无错误状态时仍然必须失败否则一个静默不渲染任何内容的回归会被掩盖。citations.test.ts 中凝练的核心模式const searchOutcome async (): Promisepending | matched | error { if ((await citation.resultTexts(5)).some((t) matcher.test(t))) return matched; if (await citation.searchError.isVisible()) return error; return pending; }; await expect.poll(searchOutcome, { timeout: 30000 }).not.toBe(pending); if ((await searchOutcome()) error) { await citation.cancel(); test.skip(true, search failed service-side from this runner); }其中searchOutcome的三态语义是整套策略的骨架matched前 5 条渲染结果中有任意一条命中关键词正则resultTexts(5)只取当前渲染窗口内的行——列表是 react-window 虚拟化的该行为在 insert_citation.page.ts 有注释说明errorsearchError可见pending两者皆无继续轮询。配套的判定工具还有前置可达性探测skipUnlessReachable()基于isServiceReachable()network.ts。该探测从 Node 发起、绕过产品本身任何 HTTP 响应包括错误状态与重定向都算可达只有 DNS、连接、TLS、超时这类网络级失败才算不可达结果按 URL 缓存在 worker 生命周期内。searchError的文本过滤定义于 insert_citation.page.ts它用正则/error occurred|Unable to search/过滤.pm-insert-citation-source-panel-list-noresults-text节点——必须过滤因为同一个节点同时承载No results...提示和进度文本直接取节点会把未出错也误判为错误。措辞来源是 panmirror 的errorForStatus()与各面板的 unknown error 兜底。运行中降级的二次探测stageFirstResultOrSkip()citations.test.ts在 30 秒内轮询pending若最终仍无结果且无错误则调用reprobeService()network.ts重新探测——探测通过时保持失败真回归探测失败则追溯性跳过服务在测试中途不可达。实战要点小结把上述模式汇集成一套可复用的检查清单进可视化模式统一用SourcePaneActions.ensureVisualMode()它自带幂等探测、工具栏就绪等待与首次切换的 pandoc 确认对话框处理新建文档优先用newMarkdownDoc这个最廉价宿主。选择器作用域panmirror 的 React UI 在 GWT[roledialog]之外一律用page级选择器只有 GWT 按钮Insert/Cancel在对话框内。树操作点击.pm-navigation-tree-node节点行而非图标树是 react-window 虚拟化的禁止连点。选中判据以源面板自身证据搜索框占位符、Search 按钮为准不要依赖.pm-selected-navigation-tree-item高亮类。初始化重置参考文献加载完成后面板会被弹回初始节点最多一次采用「可重试的点击断言单元」加「6 秒回退监控」双保险。latent search键入不触发搜索用pressSequentially非fill()输入后点 Search 按钮再expect.poll等待结果。异步 RPC搜索在 rsession 内执行、返回{asyncHandle}、结果经/events/get_events异步送达Playwright 无法拦截服务端请求只能做 Node 侧可达性探测。成败判定服务端错误 →cancel()test.skip(true, ...)超时且无结果无错误 → 保持失败必要时先reprobeService()区分服务降级与产品回归。这套模式不仅服务于引文测试也同样适用于可视化模式下 Insert/Format 菜单中的其他 React 控件菜单中的格式化命令、插入操作等是编写 RStudio panmirror 相关 Playwright 测试时应当首先阅读的行为规范。原始规范文档位于 visual-editor.md页面对象与测试实现分别见 insert_citation.page.ts 与 citations.test.ts可作为继续深入研读的入口。赞分享开发工具后端【免费下载链接】rstudioRStudio is an integrated development environment (IDE) for R项目地址https://gitcode.com/gh_mirrors/rs/rstudio点击查看免费下载相关推荐告别繁琐测试Playwright 富文本编辑器自动化测试指南告别繁琐测试Playwright 富文本编辑器自动化测试指南 你还在手动验证富文本编辑器的格式兼容性还在为不同浏览器显示差异头疼本文将用Playwrigh测试开发工具浏览器控制【rstudio】 RStudio项目中的可视化编辑器使用指南RStudio项目中的可视化编辑器使用指南 概述 RStudio的可视化编辑器为Pandoc Markdown提供了所见即所得的编辑体验。这个编辑器不仅保留了M开发工具后端CANN/asc-devkit浮点转无符号整型函数\_\_float2ull\_ru 产品支持情况 ! npu950 id1 Ascend 950PR/Ascend 950DT支持 ! end id人工智能深度学习算子库CANNAscend上一篇utiputils测试策略如何确保网络工具包的稳定性和兼容性下一篇全面解析easy-checkeropenEuler社区背后的强大检查利器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表