From f2ed4c9e6f9bab6ed5b804f07f8b7f3c3c84419b Mon Sep 17 00:00:00 2001 From: diattamo Date: Tue, 3 Mar 2026 16:52:03 -0500 Subject: [PATCH] test(ContourSegNavigation): Add e2e tests for contour segmentation navigation (#5834) --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../Viewport/ViewportActionArrows.tsx | 15 +- tests/ContourSegNavigation.spec.ts | 128 ++++++++++++++++++ tests/pages/ViewportPageObject.ts | 33 +++++ tests/utils/assertions.ts | 4 + tests/utils/getSvgPath.ts | 11 ++ tests/utils/index.ts | 4 + tests/utils/navigateWithViewportArrow.ts | 13 ++ 7 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 tests/ContourSegNavigation.spec.ts create mode 100644 tests/utils/getSvgPath.ts create mode 100644 tests/utils/navigateWithViewportArrow.ts diff --git a/platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx b/platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx index b96da1f5a..25ec5256b 100644 --- a/platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx +++ b/platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx @@ -12,11 +12,20 @@ const arrowClasses = */ function ViewportActionArrows({ onArrowsClick, className }) { return ( -
-
+
+
onArrowsClick(-1)} />
-
+
onArrowsClick(1)} />
diff --git a/tests/ContourSegNavigation.spec.ts b/tests/ContourSegNavigation.spec.ts new file mode 100644 index 000000000..3eef690a4 --- /dev/null +++ b/tests/ContourSegNavigation.spec.ts @@ -0,0 +1,128 @@ +import { expect, test, visitStudy, getSvgPath, navigateWithViewportArrow } from './utils'; +import { expectRowSelected } from './utils/assertions'; + +const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501'; + +test.beforeEach(async ({ + page, + leftPanelPageObject, + DOMOverlayPageObject, + rightPanelPageObject +}) => { + const mode = 'segmentation'; + await visitStudy(page, studyInstanceUID, mode, 2000); + await leftPanelPageObject.loadSeriesByModality('RTSTRUCT'); + await page.waitForTimeout(5000); + await DOMOverlayPageObject.viewport.segmentationHydration.yes.click(); + // Click segment 0 in the right panel to establish a known starting position + await rightPanelPageObject.contourSegmentationPanel.panel.nthSegment(0).click(); + await page.waitForTimeout(5000); +}); + +test('should navigate the contours when clicking each segments in the right panel', async ({ + rightPanelPageObject, + viewportPageObject, +}) => { + const getSegment = (index: number) => + rightPanelPageObject.contourSegmentationPanel.panel.nthSegment(index); + + const seg0 = await getSvgPath(viewportPageObject); + expect(seg0, 'Segment at index 0: expected a non-null SVG path').not.toBeNull(); + + await getSegment(3).click(); + const seg3 = await getSvgPath(viewportPageObject); + expect(seg3, 'Segment at index 3: expected a non-null SVG path').not.toBeNull(); + await expectRowSelected(getSegment(3)); + + await getSegment(2).click(); + const seg2 = await getSvgPath(viewportPageObject); + expect(seg2, 'Segment at index 2: expected a non-null SVG path').not.toBeNull(); + await expectRowSelected(getSegment(2)); + + await getSegment(1).click(); + const seg1 = await getSvgPath(viewportPageObject); + expect(seg1, 'Segment at index 1: expected a non-null SVG path').not.toBeNull(); + await expectRowSelected(getSegment(1)); + + // Clicking segments again should return the original paths + await getSegment(2).click(); + const seg2Again = await getSvgPath(viewportPageObject); + expect(seg2Again, 'Segment 2 again: expected to match the original segment 2 path').toBe(seg2); + await expectRowSelected(getSegment(2)); + + await getSegment(1).click(); + const seg1Again = await getSvgPath(viewportPageObject); + expect(seg1Again, 'Segment 1 again: expected to match the original segment 1 path').toBe(seg1); + await expectRowSelected(getSegment(1)); + + await getSegment(0).click(); + const seg0Again = await getSvgPath(viewportPageObject); + expect(seg0Again, 'Segment 0 again: expected to match the original segment 0 path').toBe(seg0); + await expectRowSelected(getSegment(0)); + + await getSegment(3).click(); + const seg3Again = await getSvgPath(viewportPageObject); + expect(seg3Again, 'Segment 3 again: expected to match the original segment 3 path').toBe(seg3); + await expectRowSelected(getSegment(3)); +}); + +test('should navigate the segmentations using the Viewport arrow buttons', async ({ + rightPanelPageObject, + viewportPageObject, +}) => { + const getSegment = (index: number) => + rightPanelPageObject.contourSegmentationPanel.panel.nthSegment(index); + + const initialSvgPath = await getSvgPath(viewportPageObject); + expect(initialSvgPath, 'Segment at index 0: expected a non-null SVG path').not.toBeNull(); + // Expect the correct segment to be selected in the right panel + await expectRowSelected(getSegment(0)); + + await navigateWithViewportArrow(viewportPageObject, 'next'); + const secondSvgPath = await getSvgPath(viewportPageObject); + expect(secondSvgPath, 'Segment at index 1: expected a different SVG path from segment 0').not.toBe(initialSvgPath); + await expectRowSelected(getSegment(1)); + + await navigateWithViewportArrow(viewportPageObject, 'next'); + const thirdSvgPath = await getSvgPath(viewportPageObject); + expect(thirdSvgPath, 'Segment at index 2: expected a different SVG path from segment 1').not.toBe(secondSvgPath); + await expectRowSelected(getSegment(2)); + + await navigateWithViewportArrow(viewportPageObject, 'next'); + const fourthSvgPath = await getSvgPath(viewportPageObject); + expect(fourthSvgPath, 'Segment at index 3: expected a different SVG path from segment 2').not.toBe(thirdSvgPath); + await expectRowSelected(getSegment(3)); + + // Wraparound test — next from last should return to first segment + await navigateWithViewportArrow(viewportPageObject, 'next'); + const svgPathWraparoundWithNext = await getSvgPath(viewportPageObject); + expect( + svgPathWraparoundWithNext, + 'Expected svg path to match the initial svg path after wrapping around with next navigation' + ).toBe(initialSvgPath); + await expectRowSelected(getSegment(0)); + + // Wraparound test — prev from first should return to last segment + await navigateWithViewportArrow(viewportPageObject, 'prev'); + const svgPathWraparoundWithPrev = await getSvgPath(viewportPageObject); + expect( + svgPathWraparoundWithPrev, + 'Expected svg path to match the fourth svg path after wrapping around with prev navigation' + ).toBe(fourthSvgPath); + await expectRowSelected(getSegment(3)); + + await navigateWithViewportArrow(viewportPageObject, 'prev'); + const backToThirdSvgPath = await getSvgPath(viewportPageObject); + expect(backToThirdSvgPath, 'Expected path to match third segment after going prev from fourth').toBe(thirdSvgPath); + await expectRowSelected(getSegment(2)); + + await navigateWithViewportArrow(viewportPageObject, 'prev'); + const backToSecondSvgPath = await getSvgPath(viewportPageObject); + expect(backToSecondSvgPath, 'Expected path to match second segment after going prev from third').toBe(secondSvgPath); + await expectRowSelected(getSegment(1)); + + await navigateWithViewportArrow(viewportPageObject, 'prev'); + const backToFirstSvgPath = await getSvgPath(viewportPageObject); + expect(backToFirstSvgPath, 'Expected path to match first segment after going prev from second').toBe(initialSvgPath); + await expectRowSelected(getSegment(0)); +}); diff --git a/tests/pages/ViewportPageObject.ts b/tests/pages/ViewportPageObject.ts index 7a8fd0c40..138ac6cf8 100644 --- a/tests/pages/ViewportPageObject.ts +++ b/tests/pages/ViewportPageObject.ts @@ -64,6 +64,17 @@ export interface IViewportPageObject { }; pane: Locator; svg: (innerElement?: SvgInnerElement) => Locator; + navigationArrows: { + locator: Locator; + prev: { + button: Locator; + click: () => Promise; + }; + next: { + button: Locator; + click: () => Promise; + }; + }; } export class ViewportPageObject { @@ -146,6 +157,27 @@ export class ViewportPageObject { return viewport.locator(`svg.svg-layer${innerElement ? ` ${innerElement}` : ''}`); } + private getNavigationArrows(viewport: Locator) { + const container = viewport.getByTestId('viewport-action-arrows'); + const prevButton = viewport.getByTestId('viewport-action-arrows-left'); + const nextButton = viewport.getByTestId('viewport-action-arrows-right'); + return { + locator: container, + prev: { + button: prevButton, + click: async () => { + await prevButton.click(); + }, + }, + next: { + button: nextButton, + click: async () => { + await nextButton.click(); + }, + }, + }; + } + private viewportPageObjectFactory(viewport: Locator): IViewportPageObject { return { nthAnnotation: (nth: number) => this.getAnnotation(viewport, nth), @@ -189,6 +221,7 @@ export class ViewportPageObject { svg: (innerElement?: SvgInnerElement) => { return this.getSvg(viewport, innerElement); }, + navigationArrows: this.getNavigationArrows(viewport), }; } diff --git a/tests/utils/assertions.ts b/tests/utils/assertions.ts index 17b2e7820..42ff6473f 100644 --- a/tests/utils/assertions.ts +++ b/tests/utils/assertions.ts @@ -61,3 +61,7 @@ export async function assertBoundingBoxIsContainedWithin({ `${innerBoxLabel} bottom edge should be within ${outerBoxLabel}` ).toBeLessThanOrEqual(outerBox.y + outerBox.height); } + +export async function expectRowSelected(rowObject) { + await expect(rowObject.locator).toContainClass('bg-popover'); +} diff --git a/tests/utils/getSvgPath.ts b/tests/utils/getSvgPath.ts new file mode 100644 index 000000000..16461e823 --- /dev/null +++ b/tests/utils/getSvgPath.ts @@ -0,0 +1,11 @@ +import { ViewportPageObject } from '../pages/ViewportPageObject'; + +const getSvgPath = async (viewportPageObject: ViewportPageObject, viewportId = 'default') => { + return viewportPageObject + .getById(viewportId) + .svg('path') + .first() + .getAttribute('d'); +}; + +export { getSvgPath }; diff --git a/tests/utils/index.ts b/tests/utils/index.ts index 6366b2a9c..50b6ba1c3 100644 --- a/tests/utils/index.ts +++ b/tests/utils/index.ts @@ -16,6 +16,8 @@ import { clearAllAnnotations } from './clearAllAnnotations'; import { scrollVolumeViewport } from './scrollVolumeViewport'; import { attemptAction } from './attemptAction'; import { addLengthMeasurement } from './addLengthMeasurement'; +import { getSvgPath } from './getSvgPath'; +import { navigateWithViewportArrow } from './navigateWithViewportArrow'; import { test, expect } from './fixture'; import { subscribeToMeasurementAdded } from './subscribeToMeasurement'; @@ -38,6 +40,8 @@ export { attemptAction, addLengthMeasurement, subscribeToMeasurementAdded, + getSvgPath, + navigateWithViewportArrow, test, expect, }; diff --git a/tests/utils/navigateWithViewportArrow.ts b/tests/utils/navigateWithViewportArrow.ts new file mode 100644 index 000000000..bdd472ba5 --- /dev/null +++ b/tests/utils/navigateWithViewportArrow.ts @@ -0,0 +1,13 @@ +import { ViewportPageObject } from '../pages/ViewportPageObject'; + +const navigateWithViewportArrow = async ( + viewportPageObject: ViewportPageObject, + direction: 'next' | 'prev', + viewportId = 'default' +) => { + const viewport = viewportPageObject.getById(viewportId); + await viewport.navigationArrows[direction].click(); +}; + +export { navigateWithViewportArrow }; +