From 2258c7957a18ed0f1a75d2bd5d379006797b2882 Mon Sep 17 00:00:00 2001 From: diattamo Date: Wed, 20 May 2026 21:18:58 -0400 Subject: [PATCH] feat(tests): Add tests for duplicating contour segments (#6002) --- tests/ContourSegNavigation.spec.ts | 104 +++++++++++++++--- tests/ContourSegmentDuplicate.spec.ts | 109 +++++++++++++++++++ tests/ContourSegmentToggleVisibility.spec.ts | 14 ++- tests/pages/ViewportPageObject.ts | 2 +- tests/utils/getSvgAttribute.ts | 23 ++++ tests/utils/getSvgPath.ts | 7 -- tests/utils/index.ts | 4 +- 7 files changed, 232 insertions(+), 31 deletions(-) create mode 100644 tests/ContourSegmentDuplicate.spec.ts create mode 100644 tests/utils/getSvgAttribute.ts delete mode 100644 tests/utils/getSvgPath.ts diff --git a/tests/ContourSegNavigation.spec.ts b/tests/ContourSegNavigation.spec.ts index 3eef690a4..18242e4aa 100644 --- a/tests/ContourSegNavigation.spec.ts +++ b/tests/ContourSegNavigation.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, visitStudy, getSvgPath, navigateWithViewportArrow } from './utils'; +import { expect, test, visitStudy, getSvgAttribute, navigateWithViewportArrow } from './utils'; import { expectRowSelected } from './utils/assertions'; const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501'; @@ -26,42 +26,74 @@ test('should navigate the contours when clicking each segments in the right pane const getSegment = (index: number) => rightPanelPageObject.contourSegmentationPanel.panel.nthSegment(index); - const seg0 = await getSvgPath(viewportPageObject); + const seg0 = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); expect(seg0, 'Segment at index 0: expected a non-null SVG path').not.toBeNull(); await getSegment(3).click(); - const seg3 = await getSvgPath(viewportPageObject); + const seg3 = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const seg2 = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const seg1 = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const seg2Again = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const seg1Again = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const seg0Again = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const seg3Again = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); expect(seg3Again, 'Segment 3 again: expected to match the original segment 3 path').toBe(seg3); await expectRowSelected(getSegment(3)); }); @@ -73,29 +105,49 @@ test('should navigate the segmentations using the Viewport arrow buttons', async const getSegment = (index: number) => rightPanelPageObject.contourSegmentationPanel.panel.nthSegment(index); - const initialSvgPath = await getSvgPath(viewportPageObject); + const initialSvgPath = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const secondSvgPath = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const thirdSvgPath = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const fourthSvgPath = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const svgPathWraparoundWithNext = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); expect( svgPathWraparoundWithNext, 'Expected svg path to match the initial svg path after wrapping around with next navigation' @@ -104,7 +156,11 @@ test('should navigate the segmentations using the Viewport arrow buttons', async // Wraparound test — prev from first should return to last segment await navigateWithViewportArrow(viewportPageObject, 'prev'); - const svgPathWraparoundWithPrev = await getSvgPath(viewportPageObject); + const svgPathWraparoundWithPrev = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); expect( svgPathWraparoundWithPrev, 'Expected svg path to match the fourth svg path after wrapping around with prev navigation' @@ -112,17 +168,29 @@ test('should navigate the segmentations using the Viewport arrow buttons', async await expectRowSelected(getSegment(3)); await navigateWithViewportArrow(viewportPageObject, 'prev'); - const backToThirdSvgPath = await getSvgPath(viewportPageObject); + const backToThirdSvgPath = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const backToSecondSvgPath = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); 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); + const backToFirstSvgPath = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); expect(backToFirstSvgPath, 'Expected path to match first segment after going prev from second').toBe(initialSvgPath); await expectRowSelected(getSegment(0)); }); diff --git a/tests/ContourSegmentDuplicate.spec.ts b/tests/ContourSegmentDuplicate.spec.ts new file mode 100644 index 000000000..27bd80e84 --- /dev/null +++ b/tests/ContourSegmentDuplicate.spec.ts @@ -0,0 +1,109 @@ +import { + expect, + test, + visitStudy, + waitForViewportsRendered, +} from './utils'; +import { getSvgAttribute } from './utils/getSvgAttribute'; + +const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501'; +const defaultSegment0Name = 'Threshold'; +const defaultSegment1Name = 'Big Sphere'; + +test.beforeEach(async ({ + page, + leftPanelPageObject, + DOMOverlayPageObject, +}) => { + const mode = 'segmentation'; + await visitStudy(page, studyInstanceUID, mode, 2000); + + await leftPanelPageObject.loadSeriesByModality('RTSTRUCT'); + await waitForViewportsRendered(page); + await expect(DOMOverlayPageObject.viewport.segmentationHydration.locator).toBeVisible(); + + await DOMOverlayPageObject.viewport.segmentationHydration.yes.click(); +}); + +test('should duplicate a contour segment and add a new row to the panel', async ({ + page, + rightPanelPageObject, +}) => { + const panel = rightPanelPageObject.contourSegmentationPanel.panel; + + const initialCount = await panel.getSegmentCount(); + expect(initialCount, 'Expected to load with 4 segments').toBe(4); + + const segment0 = panel.nthSegment(0); + await expect(segment0.title).toHaveText(defaultSegment0Name); + + await segment0.actions.duplicate(); + + const countAfterDuplicate = await panel.getSegmentCount(); + expect(countAfterDuplicate, 'Expected one additional segment row after duplicating').toBe(5); + + //New segment's default name is formatted as "Segment {segmentCount}" + const newSegmentLocator = panel.nthSegment(initialCount).title; + await expect(newSegmentLocator, 'Expected correct title for duplicated segment').toHaveText(`Segment 5`); + + // Original segment titles should be unchanged + await expect(panel.nthSegment(0).title).toHaveText(defaultSegment0Name); + await expect(panel.nthSegment(1).title).toHaveText(defaultSegment1Name); +}); + +test('should duplicate the same segment multiple times', async ({ + page, + rightPanelPageObject, +}) => { + const panel = rightPanelPageObject.contourSegmentationPanel.panel; + + const segment0 = panel.nthSegment(0); + + await segment0.actions.duplicate(); + expect(await panel.getSegmentCount(), 'Expected one additional segment row after duplicating').toBe(5); + + const firstDuplicateTitleLocator = panel.nthSegment(4).title; + await expect(firstDuplicateTitleLocator, 'Expected correct title for first duplicated segment').toHaveText(`Segment 5`); + + await segment0.actions.duplicate(); + expect(await panel.getSegmentCount(), 'Expected another segment row after duplicating the same segment again').toBe(6); + + const secondDuplicateTitleLocator = panel.nthSegment(5).title; + await expect(secondDuplicateTitleLocator, 'Expected correct title for second duplicated segment').toHaveText(`Segment 6`); +}); + +test('should render the duplicated contour on the viewport', async ({ + rightPanelPageObject, + viewportPageObject, +}) => { + const panel = rightPanelPageObject.contourSegmentationPanel.panel; + + // Hide everything, to be able to grab only the SVG path of the segment to duplicate + await rightPanelPageObject.contourSegmentationPanel.segmentsVisibilityToggle.click(); + const segment0 = panel.nthSegment(0); + await segment0.toggleVisibility(); + await segment0.click(); + + const sourceSvgPath = await getSvgAttribute({viewportPageObject, svgInnerElement: 'path', attributeName: 'd'}); + expect(sourceSvgPath, 'Expected a visible SVG path for the source segment').not.toBeNull(); + const sourceSvgPaths = (await viewportPageObject.getById('default')).svg('path'); + expect(sourceSvgPaths, 'Expected only one SVG path element for the original segment').toHaveCount(1); + + // New segment is at index 4 + await segment0.actions.duplicate(); + const duplicatedSegment = panel.nthSegment(4); + + // Hide again to show duplicate only + await segment0.toggleVisibility(); + await duplicatedSegment.click(); + + const duplicatedSvgPath = await getSvgAttribute({viewportPageObject, svgInnerElement: 'path', attributeName: 'd'}); + expect(duplicatedSvgPath, 'Expected a visible SVG path for the duplicated segment').not.toBeNull(); + const duplicatedSvgPaths = (await viewportPageObject.getById('default')).svg('path'); + expect(duplicatedSvgPaths, 'Expected only one SVG path element for the duplicated segment').toHaveCount(1); + + expect( + duplicatedSvgPath, + 'Expected the duplicated segment to have the same SVG path as the source' + ).toBe(sourceSvgPath); +}); diff --git a/tests/ContourSegmentToggleVisibility.spec.ts b/tests/ContourSegmentToggleVisibility.spec.ts index a4c6a65d9..722faf13b 100644 --- a/tests/ContourSegmentToggleVisibility.spec.ts +++ b/tests/ContourSegmentToggleVisibility.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, visitStudy, getSvgPath, navigateWithViewportArrow } from './utils'; +import { expect, test, visitStudy, getSvgAttribute, navigateWithViewportArrow } from './utils'; const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501'; @@ -78,7 +78,11 @@ test('should restore svg paths when segment visibility is toggled on/off', async const segment0 = rightPanelPageObject.contourSegmentationPanel.panel.nthSegment(0); await segment0.toggleVisibility(); - const svgPathBefore = await getSvgPath(viewportPageObject); + const svgPathBefore = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); expect(svgPathBefore, 'Expected a visible SVG path for segment 0').not.toBeNull(); await segment0.toggleVisibility(); @@ -88,7 +92,11 @@ test('should restore svg paths when segment visibility is toggled on/off', async ).toHaveCount(0); await segment0.toggleVisibility(); - const svgPathAfter = await getSvgPath(viewportPageObject); + const svgPathAfter = await getSvgAttribute({ + viewportPageObject, + svgInnerElement: 'path', + attributeName: 'd', + }); expect(svgPathAfter, 'Expected SVG path to be restored after toggling visibility back on').toBe( svgPathBefore ); diff --git a/tests/pages/ViewportPageObject.ts b/tests/pages/ViewportPageObject.ts index 16396b4fc..8637b0388 100644 --- a/tests/pages/ViewportPageObject.ts +++ b/tests/pages/ViewportPageObject.ts @@ -11,7 +11,7 @@ import { DataOverlayPageObject } from './DataOverlayPageObject'; import { DOMOverlayPageObject } from './DOMOverlayPageObject'; import { MagnifyGlassPageObject } from './MagnifyGlassPageObject'; -type SvgInnerElement = 'circle' | 'path' | 'd' | 'line' | 'g'; +export type SvgInnerElement = 'circle' | 'path' | 'line' | 'g'; type NormalizedDragParams = { start: { x: number; y: number }; diff --git a/tests/utils/getSvgAttribute.ts b/tests/utils/getSvgAttribute.ts new file mode 100644 index 000000000..66a24bc53 --- /dev/null +++ b/tests/utils/getSvgAttribute.ts @@ -0,0 +1,23 @@ +import { SvgInnerElement, ViewportPageObject } from '../pages/ViewportPageObject'; + +const getSvgAttribute = async ({ + viewportPageObject, + svgInnerElement, + attributeName, + viewportId = 'default', + nth = 0, +}: { + viewportPageObject: ViewportPageObject; + svgInnerElement: SvgInnerElement; + attributeName: string; + viewportId?: string; + nth?: number; +}) => { + return (await viewportPageObject.getById(viewportId)) + .svg(svgInnerElement) + .nth(nth) + .getAttribute(attributeName); +}; + + +export { getSvgAttribute }; diff --git a/tests/utils/getSvgPath.ts b/tests/utils/getSvgPath.ts deleted file mode 100644 index 1fb6359e3..000000000 --- a/tests/utils/getSvgPath.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ViewportPageObject } from '../pages/ViewportPageObject'; - -const getSvgPath = async (viewportPageObject: ViewportPageObject, viewportId = 'default') => { - return (await viewportPageObject.getById(viewportId)).svg('path').first().getAttribute('d'); -}; - -export { getSvgPath }; diff --git a/tests/utils/index.ts b/tests/utils/index.ts index 87221c371..a7f7e46a5 100644 --- a/tests/utils/index.ts +++ b/tests/utils/index.ts @@ -24,7 +24,7 @@ import { clearAllAnnotations } from './clearAllAnnotations'; import { scrollVolumeViewport } from './scrollVolumeViewport'; import { attemptAction } from './attemptAction'; import { addLengthMeasurement } from './addLengthMeasurement'; -import { getSvgPath } from './getSvgPath'; +import { getSvgAttribute } from './getSvgAttribute'; import { navigateWithViewportArrow } from './navigateWithViewportArrow'; import { test, expect } from './fixture'; import { subscribeToMeasurementAdded } from './subscribeToMeasurement'; @@ -57,7 +57,7 @@ export { attemptAction, addLengthMeasurement, subscribeToMeasurementAdded, - getSvgPath, + getSvgAttribute, navigateWithViewportArrow, waitForAnyViewportNeedsRender, waitForViewportsRendered,