ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

OHIF Viewer Playwright 页面对象(Page Object)体系指南:fixture 注入、访问惯例与扩展规则

2026/9/18 18:34:23 拓冰建站 浏览量
OHIF Viewer Playwright 页面对象(Page Object)体系指南:fixture 注入、访问惯例与扩展规则 OHIF Viewer Playwright 页面对象Page Object体系指南fixture 注入、访问惯例与扩展规则【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers导读OHIF Viewer 的端到端测试不是普通的 Playwright 脚本而是一套建立在自定义 fixture 注入与页面对象Page Object之上的体系。本指南以.agents/skills/ohif-test-agent/references/page-objects.md为骨架结合tests/pages/下的真实源码系统讲解如何发现页面对象 API、六种 fixture 键的正确解构方式、视口包装器与视口实例的区别、面板访问三步惯例以及当控件未被覆盖时如何扩展页面对象而不是在 spec 里内联裸选择器。读完你不仅能读懂任何现有 spec还能以与既有代码无差别的方式为 OHIF 测试套件新增页面对象能力。一、这份文档的定位只记录源码无法直接推导的稳定规则page-objects.md 开篇就划清了边界它故意不列出任何类的方法清单只记录那些仅读单个源码文件无法推导出来的结构性规则。原因很直白——类在持续演进任何静态方法表都会在重构发生的那一刻过期而源码不会。因此本文档确立的第一原则是关于任何类当前的方法与属性以tests/pages/下的源码为唯一权威。页面对象文件的命名与类名一一对应ViewportPageObject在 tests/pages/ViewportPageObject.tsMainToolbarPageObject在 tests/pages/MainToolbarPageObject.ts等等。所有这些类统一从 tests/pages/index.ts 的 barrel 导出。文档只记录四类稳定规则本文接下来逐条展开fixture 键的拼写区分大小写哪些页面对象通过 fixture 注入、哪些通过访问器到达视口包装器与视口实例、面板访问顺序、布局标识符、子工具下拉等访问惯例控件未被覆盖时的扩展约定。二、如何发现页面对象的 API文档给出了四条发现路径是读源码方法论的具体化按类名定位文件在tests/pages/下找到对应类文件名与类名一致。从头到尾通读一遍大多数类只有几百行一次读完成本很低。留意组合子对象有些类会组合其他页面对象。例如RightPanelPageObject内部持有measurementsPanel、contourSegmentationPanel、labelMapSegmentationPanel、tmtvPanel、microscopyPanel等子面板——这些子对象通常位于同一文件或tests/pages/的兄弟文件中。在 tests/pages/RightPanelPageObject.ts 中可以看到这些子面板全部通过类内的 getter 暴露并共享getPanelRowDataObject、getPanelRowByIdx、getPanelRowByText等私有行级方法。用真实调用验证签名grep tests/或在 patterns-by-feature.md 列出的种子 spec 中查看实际用法。真实用法永远比手写签名可靠因为 spec 与 API 是协同演进的。这条方法论也呼应了整个 skill 的设计理念不要试图从参考文件记忆方法面参考文件只负责把你导向正确的源码文件。三、Fixture 注入的页面对象键名区分大小写3.1 六个 fixture 键这些页面对象通过 tests/utils/fixture.ts 注入测试函数。测试函数的第一个参数对象形式解构它们即可——绝不能手动newtest(my test, async ({ page, viewportPageObject, mainToolbarPageObject, leftPanelPageObject, rightPanelPageObject, DOMOverlayPageObject, // 注意大写 D notFoundStudyPageObject, }) { ... });六个键及注意事项Fixture 键注入的类viewportPageObjectViewportPageObjectmainToolbarPageObjectMainToolbarPageObjectleftPanelPageObjectLeftPanelPageObjectrightPanelPageObjectRightPanelPageObjectDOMOverlayPageObjectDOMOverlayPageObject大写 DnotFoundStudyPageObjectNotFoundStudyPageObject3.2 为什么不能newfixture 负责接线fixture.ts的源码揭示了原因。它基于playwright-test-coverage的test扩展出 fixture每个页面对象在创建时被绑定到当前测试的pageviewportPageObject: async ({ page }, use) { await use(new ViewportPageObject(page)); }, DOMOverlayPageObject: async ({ page }, use) { await use(new DOMOverlayPageObject(page)); },手动new ViewportPageObject(page)会跳过这层接线导致某些子对象无法正确解析到正确的page。此外 fixture 还自动注入了_applyGlobalE2EOHIFBaselineauto: true在每次测试前通过addOHIFConfiguration应用全局 E2E 基线配置。3.3 大写 D 的坑DOMOverlayPageObject的D 必须大写。文档特别提示解构出undefined且毫无报错静默失败的情况几乎总是这里的大小写笔误——因为 fixture 键不存在时解构只是得到undefinedTypeScript 不会在运行时帮你兜底。3.4 fixture 文件更新时如果 fixture 文件更新、新增了键它们会第一时间出现在fixture.ts的类型定义PageObjects中。文档建议如果感觉有东西缺失先去查 fixture 文件而不是猜。四、非 fixture 注入的页面对象通过访问器到达有两个页面对象类不通过 fixture 注入而是经由某个注入的 fixture 到达页面对象到达路径DicomTagBrowserPageObjectDOMOverlayPageObject.dialog.dicomTagBrowserDataOverlayPageObjectviewportPageObject.getById(viewportId).overlayMenu.dataOverlay源码验证在 tests/pages/DOMOverlayPageObject.ts 中dialoggetter 里直接return new DicomTagBrowserPageObject(page)在 tests/pages/ViewportPageObject.ts 的getOverlayMenu中dataOverlay通过new DataOverlayPageObject(this.page, await this.getViewportId(viewport))构造并把视口 ID 一并传入——这正是DataOverlayPageObject的menu、toggle等方法需要用dataOverlayMenu-${viewportId}-btn这类带 ID 的测试选择器定位具体视口菜单的原因见 tests/pages/DataOverlayPageObject.ts。两个类都可以手动构造如new DataOverlayPageObject(page)文档也承认测试真的需要全新实例时可以这样做但访问器路径才是惯用法——它保证视口 ID 接线正确。五、视口包装器 vs 视口实例先取实例再调方法viewportPageObject是一个包装器它本身不承载交互方法。几乎所有用例的第一步都是从网格中取一个具体的视口实例await viewportPageObject.active; // 当前聚焦的视口 viewportPageObject.getAll(); // 网格中的所有视口 viewportPageObject.getNth(i); // 第 i 个视口从 0 开始 viewportPageObject.getById(cornerstoneViewportId); // 如 default、ctAXIAL从 ViewportPageObject.ts 源码看active定位[data-cyviewport-pane][data-is-activetrue]getAll()通过getByTestId(viewport-pane).all()收集全部视口并逐个工厂化getNth(index)走getByTestId(viewport-pane).nth(index)getById(viewportId)使用[data-cyviewport-pane]:has(div[data-viewportid${viewportId}])精确匹配 Cornerstone 视口 ID。工厂方法viewportPageObjectFactory返回的实例才是携带交互能力的对象它包含交互clickAt、doubleClickAt、normalizedClickAt、normalizedDragAt、normalizedPathDragAt标注nthAnnotation(nth)返回含click、contextMenu.open、text.click的子对象文本overlayText.topLeft/topRight/bottomLeft/bottomRight每个含windowLevel、instanceNumber覆盖层overlayMenu.dataOverlay、overlayMenu.orientation、overlayMenu.windowLevel其他orientationMarkers、navigationArrows、sliceNavigationtoSlice/toFirstSlice/toLastSlice/scrollBy、magnifyGlass、pane、svg以及hide/show*Text一组文本显隐方法。其中sliceNavigation的实现值得一提toSlice通过page.evaluate调用window.commandsManager的jumpToImage命令scrollBy则直接操作window.services.cornerstoneViewportService.getCornerstoneViewport(viewportId).scroll(delta)。源码注释明确提醒await 这些方法并不保证视口已渲染完成需要像素稳定状态时还得配合waitForViewportsRendered。文档给出的核心建议是先取视口实例再在实例上调用方法——直接对包装器调用交互方法是个常见误区。六、面板访问顺序三步惯例rightPanelPageObject的每个子面板都遵循同一个三步惯例打开侧边面板toggle()选中子面板标签页.select()与.panel.*交互。文档指出跳过前两步是element not found类失败最常见的原因。规范示例await rightPanelPageObject.toggle(); await rightPanelPageObject.measurementsPanel.select(); const count await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount();从源码看toggle()点击side-panel-header-right打开右侧面板measurementsPanel.select()点击trackedMeasurements-btn随后通过panel.getMeasurementCount()、panel.nthMeasurement(i)读取测量行。行级子对象暴露actions.rename|delete|toggleLock|duplicate、title、lockIcon、toggleVisibility、click()跳转到该测量等能力。各面板的行/操作方法各不相同——需要哪个就看对应源码文件。子面板全貌RightPanelPageObject.tsmeasurementsPanel测量列表contourSegmentationPanel轮廓分割RTSTRUCT含tools.splineContour/livewireContour/freehandContour、config、combineContours、smoothContourslabelMapSegmentationPanel标签图分割SEG含tools.brush/eraser/threshold每个工具都带setRadius与config透明度、边框、非活动段透明度noToolsSegmentationPanel无工具的分割面板tmtvPanelTMTV 面板exportTmtvCsvReport、tools.brush、tools.rectangleROIThresholdmicroscopyPanel显微镜测量面板。七、布局标识符是 camelCase JS 属性布局选择通过mainToolbarPageObject.layoutSelection.layout.click()完成布局必须以camelCase 属性名访问await mainToolbarPageObject.layoutSelection.threeDFourUp.click(); await mainToolbarPageObject.layoutSelection.axialPrimary.click();不要用方括号转义的 DICOM 风格字符串如[3DFourUp]。这是类暴露工具的方式所强制的约定。从 MainToolbarPageObject.ts 源码看layoutSelection提供axialPrimaryAxial Primary、MPR、threeDFourUp3D four up、threeDMain3D main、threeDOnly3D only、threeDPrimary3D primary通用的grid(cols, rows)点击Layout后按下Layout-${cols-1}-${rows-1}的测试选择器实现任意网格布局底层的button与click()只打开布局菜单。每个具名布局的click()都封装了先打开 Layout 菜单、再点具体布局两步。八、子工具自动打开所属下拉菜单嵌套在工具栏下拉菜单中的工具测量工具、更多工具、布局各自暴露的.click()会自动帮你打开父级菜单——几乎不需要先手动展开菜单await mainToolbarPageObject.measurementTools.length.click(); // 展开 选中一步完成从源码看measurementTools的每个子工具 getter 都遵循同一模式await measurementTools.click()点击MeasurementTools-split-button-secondary展开下拉然后await button.click()点击具体工具项。覆盖的工具包括arrowAnnotate、bidirectional、circleROI、ellipticalROI、length、livewireContour、rectangleROI、splineROI、freehandROI以及显微镜专用的line.last()定位下拉中的菜单项而非分裂按钮主区两者共用同一>// spec 只描述意图 await DOMOverlayPageObject.optionsMenu.settings.click(); await DOMOverlayPageObject.dialog.userPreferences.hotkey(Zoom).set(q); await mainToolbarPageObject.zoom.click(); // 在视口上拖拽并断言缩放真实生效这与 SKILL.md 中断言真实效果而非仅断言data-active属性的原则一脉相承按钮点亮不等于缩放生效要在视口上拖拽并用视口级截图或可度量的状态变化来验证。十、页面对象地图每个类负责什么下表用于帮助你选择打开哪个文件而非枚举方法任何具体方法的权威来源仍是.ts文件本身。类文件覆盖范围ViewportPageObjecttests/pages/ViewportPageObject.tsCornerstone 视口——点击、拖拽、覆盖层、标注、十字线MainToolbarPageObjecttests/pages/MainToolbarPageObject.ts顶部工具栏——测量工具、更多工具、布局、十字线、平移LeftPanelPageObjecttests/pages/LeftPanelPageObject.ts研究浏览器——缩略图、按模态或描述加载系列RightPanelPageObjecttests/pages/RightPanelPageObject.ts侧面板——测量、轮廓分割、标签图分割、TMTV、显微镜DOMOverlayPageObjecttests/pages/DOMOverlayPageObject.tsDOM 覆盖层——对话框、hydration/跟踪提示、右键菜单、标签浏览器访问器NotFoundStudyPageObjecttests/pages/NotFoundStudyPageObject.ts研究未找到错误页DicomTagBrowserPageObjecttests/pages/DicomTagBrowserPageObject.ts标签浏览器对话框非 fixture经DOMOverlayPageObject.dialog到达DataOverlayPageObjecttests/pages/DataOverlayPageObject.ts数据覆盖层菜单非 fixture经viewport.overlayMenu到达如果目录新增或重命名了文件那个 diff 是第一线索这张表是第二线索——以目录为准。这与文档开篇源码永远权威的立场完全一致表格的价值在于引导不在于充当静态目录。十一、实战串联读透一个种子 spec把以上规则放到一个真实用例里验证。tests/Length.spec.ts 是简单测量工具的种子 spec几乎用到了本文所有规则test.beforeEach(async ({ page }) { const studyInstanceUID 1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5; const mode viewer; await visitStudy(page, studyInstanceUID, mode, 2000); }); test(should display the length tool, async ({ page, DOMOverlayPageObject, // 大写 D 的 fixture 键 mainToolbarPageObject, rightPanelPageObject, viewportPageObject, }) { await mainToolbarPageObject.measurementTools.length.click(); // 子工具自动展开下拉 const activeViewport await viewportPageObject.active; // 先取视口实例 await activeViewport.clickAt([ { x: 364, y: 234 }, { x: 544, y: 232 }, ]); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click(); // 首次测量触发跟踪提示 await checkForViewportScreenshot({ page, viewport: activeViewport, screenshotPath: screenShotPaths.length.lengthDisplayedCorrectly, }); await rightPanelPageObject.measurementsPanel.select(); // 面板访问select 后交互 // ... 通过 expectAnnotationStatsText 同时断言面板统计与 SVG 文本 });对照规则逐条印证fixture 解构六个键从./utils导入的test中解构DOMOverlayPageObject大写 D包装器 → 实例viewportPageObject.active先取活动视口再在实例上clickAt子工具下拉measurementTools.length.click()一步完成展开与选中hydration/跟踪提示首次测量后点击measurementTracking.confirm面板三步measurementsPanel.select()后通过.panel断言截图对象形式 视口级范围checkForViewportScreenshot捕获视口自动隐藏覆盖层文本路径使用screenShotPaths.category.name键而非手写字符串。这套规则同样适用于更复杂的场景RTSTRUCT 轮廓用rightPanelPageObject.contourSegmentationPanel.panel.nthSegment(i)/.segmentByText(Small Sphere)SEG 标签图用labelMapSegmentationPanel.tools.brush.setRadius(n)3D/MPR 布局用layoutSelection.threeDFourUp.click()DICOM 标签浏览器用mainToolbarPageObject.moreTools.tagBrowser.click()后经DOMOverlayPageObject.dialog.dicomTagBrowser交互。每个功能区的种子 spec 索引都收录在 patterns-by-feature.md 中编写新测试前按图索骥即可。结语OHIF 的页面对象体系用一个原则贯穿始终源码是权威spec 是活文档参考文件只记录规则。掌握 fixture 键的拼写、包装器与实例的区分、面板访问三步惯例、camelCase 布局访问与子工具自动下拉这些稳定规则之后无论是阅读现有测试还是为套件新增覆盖你都能自然地落入既有模式——新增控件时扩展页面对象、补充data-cy、镜像既有形状让 spec 保持读起来是步骤而不是选择器的长期可维护性。【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考