test: add E2E test for contour combine intersect and subtract operations (#6131)

This commit is contained in:
Ghadeer Albattarni 2026-07-11 02:21:39 -04:00 committed by GitHub
parent f0e2f64397
commit 01939e2236
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 189 additions and 4 deletions

View File

@ -70,7 +70,10 @@ function SegmentSelector({
onValueChange={onValueChange} onValueChange={onValueChange}
value={value} value={value}
> >
<SelectTrigger className="overflow-hidden"> <SelectTrigger
className="overflow-hidden"
data-cy={`logical-contour-segment-${label.toLowerCase()}-trigger`}
>
<SelectValue placeholder={t(placeholder)} /> <SelectValue placeholder={t(placeholder)} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -180,6 +183,7 @@ function LogicalContourOperationOptions() {
value={value} value={value}
key={`logical-contour-operation-${value}`} key={`logical-contour-operation-${value}`}
onClick={() => setOperation(option)} onClick={() => setOperation(option)}
data-cy={`logical-contour-operation-${value}`}
> >
<Icons.ByName name={icon}></Icons.ByName> <Icons.ByName name={icon}></Icons.ByName>
</TabsTrigger> </TabsTrigger>
@ -207,6 +211,7 @@ function LogicalContourOperationOptions() {
/> />
<div className="flex justify-end pl-[34px]"> <div className="flex justify-end pl-[34px]">
<Button <Button
data-cy="apply-logical-contour-operation"
className="border-primary/60 grow border" className="border-primary/60 grow border"
variant="ghost" variant="ghost"
onClick={() => { onClick={() => {
@ -221,6 +226,7 @@ function LogicalContourOperationOptions() {
<div className="flex items-center justify-start gap-2"> <div className="flex items-center justify-start gap-2">
<Switch <Switch
id="logical-contour-operations-create-new-segment-switch" id="logical-contour-operations-create-new-segment-switch"
data-cy="logical-contour-create-new-segment-switch"
onCheckedChange={setCreateNewSegment} onCheckedChange={setCreateNewSegment}
></Switch> ></Switch>
<Label htmlFor="logical-contour-operations-create-new-segment-switch"> <Label htmlFor="logical-contour-operations-create-new-segment-switch">

View File

@ -72,13 +72,22 @@ export const SegmentationTableConfig: React.FC<{ children?: React.ReactNode }> =
}} }}
> >
<TabsList> <TabsList>
<TabsTrigger value="fill-and-outline"> <TabsTrigger
value="fill-and-outline"
data-cy={`segmentation-config-display-fill-and-outline${dataCyTypeSuffix}`}
>
<Icons.FillAndOutline className="text-primary" /> <Icons.FillAndOutline className="text-primary" />
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="outline"> <TabsTrigger
value="outline"
data-cy={`segmentation-config-display-outline${dataCyTypeSuffix}`}
>
<Icons.OutlineOnly className="text-primary" /> <Icons.OutlineOnly className="text-primary" />
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="fill"> <TabsTrigger
value="fill"
data-cy={`segmentation-config-display-fill${dataCyTypeSuffix}`}
>
<Icons.FillOnly className="text-primary" /> <Icons.FillOnly className="text-primary" />
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>

View File

@ -0,0 +1,118 @@
import {
test,
expect,
waitForViewportRenderCycle,
checkForScreenshot,
screenShotPaths,
visitStudyAndHydrate,
} from './utils';
const segments = {
bigSphere: 'Big Sphere',
smallSphere: 'Small Sphere',
result: 'Segment 5',
} as const;
test.beforeEach(
async ({ page, leftPanelPageObject, DOMOverlayPageObject, rightPanelPageObject }) => {
const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501';
await visitStudyAndHydrate({
page,
leftPanelPageObject,
DOMOverlayPageObject,
studyInstanceUID,
modality: 'RTSTRUCT',
});
}
);
test.describe('Intersect operation', () => {
test('intersect Big Sphere with Small Sphere (nested, no edge crossing) produces Small Sphere', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
const contourSegmentationPanel = rightPanelPageObject.contourSegmentationPanel;
const activeViewport = await viewportPageObject.active;
await activeViewport.pane.dblclick();
await contourSegmentationPanel.config.toggle.click();
await contourSegmentationPanel.config.display.fillAndOutline();
await contourSegmentationPanel.panel.segmentByText(segments.bigSphere).click();
const combineContours = rightPanelPageObject.contourSegmentationPanel.combineContours;
await combineContours.open();
await combineContours.selectOperation('intersect');
await combineContours.selectSegmentA(segments.bigSphere);
await combineContours.selectSegmentB(segments.smallSphere);
await combineContours.enableCreateNewSegment();
let viewportRenderCycle = waitForViewportRenderCycle(page);
await combineContours.apply();
await viewportRenderCycle;
await expect(contourSegmentationPanel.panel.rows).toHaveCount(5);
await expect(contourSegmentationPanel.panel.nthSegment(4).title).toHaveText(segments.result);
await contourSegmentationPanel.segmentsVisibilityToggle.click();
viewportRenderCycle = waitForViewportRenderCycle(page);
await contourSegmentationPanel.panel.segmentByText(segments.result).toggleVisibility();
await viewportRenderCycle;
await checkForScreenshot(
page,
viewportPageObject.grid,
screenShotPaths.contourCombineOperations.intersectBigSphereSmallSphereResult
);
});
});
test.describe('Subtract operation', () => {
test('subtract Big Sphere minus Small Sphere (nested, no edge crossing) produces Big Sphere with a hole', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
const contourSegmentationPanel = rightPanelPageObject.contourSegmentationPanel;
const activeViewport = await viewportPageObject.active;
await activeViewport.pane.dblclick();
await contourSegmentationPanel.config.toggle.click();
await contourSegmentationPanel.config.display.fillAndOutline();
await contourSegmentationPanel.panel.segmentByText(segments.bigSphere).click();
const combineContours = rightPanelPageObject.contourSegmentationPanel.combineContours;
await combineContours.open();
await combineContours.selectOperation('subtract');
await combineContours.selectSegmentA(segments.bigSphere);
await combineContours.selectSegmentB(segments.smallSphere);
await combineContours.enableCreateNewSegment();
let viewportRenderCycle = waitForViewportRenderCycle(page);
await combineContours.apply();
await viewportRenderCycle;
await expect(contourSegmentationPanel.panel.rows).toHaveCount(5);
await expect(contourSegmentationPanel.panel.nthSegment(4).title).toHaveText(segments.result);
await contourSegmentationPanel.segmentsVisibilityToggle.click();
viewportRenderCycle = waitForViewportRenderCycle(page);
await contourSegmentationPanel.panel.segmentByText(segments.result).toggleVisibility();
await viewportRenderCycle;
await checkForScreenshot(
page,
viewportPageObject.grid,
screenShotPaths.contourCombineOperations.subtractBigSphereMinusSmallSphereResult
);
});
});

View File

@ -395,6 +395,54 @@ export class RightPanelPageObject {
}; };
}, },
}, },
get config() {
const configToggle = page.getByTestId('segmentation-config-toggle-Contour');
return {
toggle: {
locator: configToggle,
click: async () => {
await configToggle.click();
},
},
display: {
fillAndOutline: async () => {
await page
.getByTestId(`segmentation-config-display-fill-and-outline-Contour`)
.click();
},
outline: async () => {
await page.getByTestId(`segmentation-config-display-outline-Contour`).click();
},
fill: async () => {
await page.getByTestId(`segmentation-config-display-fill-Contour`).click();
},
},
};
},
get combineContours() {
return {
open: async () => {
await page.getByTestId('LogicalContourOperations').click();
},
selectOperation: async (operation: 'merge' | 'intersect' | 'subtract') => {
await page.getByTestId(`logical-contour-operation-${operation}`).click();
},
selectSegmentA: async (label: string) => {
await page.getByTestId('logical-contour-segment-a-trigger').click();
await page.getByRole('option', { name: label }).click();
},
selectSegmentB: async (label: string) => {
await page.getByTestId('logical-contour-segment-b-trigger').click();
await page.getByRole('option', { name: label }).click();
},
apply: async () => {
await page.getByTestId('apply-logical-contour-operation').click();
},
enableCreateNewSegment: async () => {
await page.getByTestId('logical-contour-create-new-segment-switch').click();
},
};
},
}; };
} }
get labelMapSegmentationPanel() { get labelMapSegmentationPanel() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -221,6 +221,10 @@ const screenShotPaths = {
overlaysDisplayed: 'overlaysDisplayed.png', overlaysDisplayed: 'overlaysDisplayed.png',
overlaySEGsAndRTDisplayed: 'overlaySEGsAndRTDisplayed.png', overlaySEGsAndRTDisplayed: 'overlaySEGsAndRTDisplayed.png',
}, },
contourCombineOperations: {
subtractBigSphereMinusSmallSphereResult: 'subtractBigSphereMinusSmallSphereResult.png',
intersectBigSphereSmallSphereResult: 'intersectBigSphereSmallSphereResult.png',
},
workList: { workList: {
scrollBarRenderedProperly: 'scrollBarRenderedProperly.png', scrollBarRenderedProperly: 'scrollBarRenderedProperly.png',
}, },