tests(Playwright): Full implementation of Page Object Models (POMs) to Playwright tests (#5608)

This commit is contained in:
Vinícius Alves de Faria Resende 2025-12-12 13:46:45 -03:00 committed by GitHub
parent 7b12dcf048
commit d917e74e68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 1167 additions and 496 deletions

View File

@ -12,8 +12,14 @@ export function UntrackSeriesModal({ hide, onConfirm, message }) {
</div> </div>
<FooterAction className="mt-4"> <FooterAction className="mt-4">
<FooterAction.Right> <FooterAction.Right>
<FooterAction.Secondary onClick={hide}>Cancel</FooterAction.Secondary> <FooterAction.Secondary
dataCY="untracked-series-modal-cancel-button"
onClick={hide}
>
Cancel
</FooterAction.Secondary>
<FooterAction.Primary <FooterAction.Primary
dataCY="untracked-series-modal-confirm-button"
onClick={() => { onClick={() => {
onConfirm(); onConfirm();
hide(); hide();

View File

@ -315,6 +315,7 @@ const DataRowComponent = React.forwardRef<HTMLDivElement, DataRowProps>(
isSelected || !isVisible ? 'opacity-100' : 'opacity-0 group-hover:opacity-100' isSelected || !isVisible ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
}`} }`}
aria-label={isVisible ? t('Hide') : t('Show')} aria-label={isVisible ? t('Hide') : t('Show')}
dataCY="data-row-visibility-toggle"
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
onToggleVisibility(e); onToggleVisibility(e);

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Button } from '../Button/Button'; import { Button, ButtonProps } from '../Button/Button';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
interface FooterActionProps { interface FooterActionProps {
@ -7,7 +7,7 @@ interface FooterActionProps {
className?: string; className?: string;
} }
interface ActionProps extends FooterActionProps { interface ActionProps extends ButtonProps {
onClick: () => void; onClick: () => void;
className?: string; className?: string;
} }
@ -60,12 +60,18 @@ FooterAction.Right = ({ children }: FooterActionProps) => {
FooterAction.Right.displayName = 'FooterAction.Right'; FooterAction.Right.displayName = 'FooterAction.Right';
// Primary action: Solid button (default) // Primary action: Solid button (default)
FooterAction.Primary = ({ children, onClick, className = 'min-w-[80px]' }: ActionProps) => { FooterAction.Primary = ({
children,
onClick,
className = 'min-w-[80px]',
...rest
}: ActionProps) => {
return ( return (
<Button <Button
variant="default" variant="default"
onClick={onClick} onClick={onClick}
className={className} className={className}
{...rest}
> >
{children} {children}
</Button> </Button>
@ -74,12 +80,18 @@ FooterAction.Primary = ({ children, onClick, className = 'min-w-[80px]' }: Actio
FooterAction.Primary.displayName = 'FooterAction.Primary'; FooterAction.Primary.displayName = 'FooterAction.Primary';
// Secondary action: Ghost button // Secondary action: Ghost button
FooterAction.Secondary = ({ children, onClick, className = 'min-w-[80px]' }: ActionProps) => { FooterAction.Secondary = ({
children,
onClick,
className = 'min-w-[80px]',
...rest
}: ActionProps) => {
return ( return (
<Button <Button
variant="secondary" variant="secondary"
onClick={onClick} onClick={onClick}
className={className} className={className}
{...rest}
> >
{children} {children}
</Button> </Button>
@ -88,12 +100,13 @@ FooterAction.Secondary = ({ children, onClick, className = 'min-w-[80px]' }: Act
FooterAction.Secondary.displayName = 'FooterAction.Secondary'; FooterAction.Secondary.displayName = 'FooterAction.Secondary';
// Tertiary action: Ghost button with different styling // Tertiary action: Ghost button with different styling
FooterAction.Auxiliary = ({ children, onClick, className }: ActionProps) => { FooterAction.Auxiliary = ({ children, onClick, className, ...rest }: ActionProps) => {
return ( return (
<Button <Button
variant="ghost" variant="ghost"
onClick={onClick} onClick={onClick}
className={className} className={className}
{...rest}
> >
{children} {children}
</Button> </Button>

View File

@ -88,7 +88,12 @@ const Thumbnail = ({
loadingProgress && loadingProgress < 1 && 'bg-primary/25' loadingProgress && loadingProgress < 1 && 'bg-primary/25'
)} )}
></div> ></div>
<div className="text-[11px] font-semibold text-white">{modality}</div> <div
className="text-[11px] font-semibold text-white"
data-cy="series-modality-label"
>
{modality}
</div>
</div> </div>
{/* top right */} {/* top right */}
@ -139,7 +144,10 @@ const Thumbnail = ({
<Tooltip> <Tooltip>
<TooltipContent>{description}</TooltipContent> <TooltipContent>{description}</TooltipContent>
<TooltipTrigger> <TooltipTrigger>
<div className="min-h-[18px] w-[128px] overflow-hidden text-ellipsis whitespace-nowrap pb-0.5 pl-1 text-left text-[12px] font-normal leading-4 text-white"> <div
className="min-h-[18px] w-[128px] overflow-hidden text-ellipsis whitespace-nowrap pb-0.5 pl-1 text-left text-[12px] font-normal leading-4 text-white"
data-cy="series-description-label"
>
{description} {description}
</div> </div>
</TooltipTrigger> </TooltipTrigger>
@ -180,11 +188,19 @@ const Thumbnail = ({
></div> ></div>
<div className="flex h-full w-[calc(100%-12px)] flex-col justify-start"> <div className="flex h-full w-[calc(100%-12px)] flex-col justify-start">
<div className="flex items-center gap-[7px]"> <div className="flex items-center gap-[7px]">
<div className="text-[13px] font-semibold text-white">{modality}</div> <div
className="text-[13px] font-semibold text-white"
data-cy="series-modality-label"
>
{modality}
</div>
<Tooltip> <Tooltip>
<TooltipContent>{description}</TooltipContent> <TooltipContent>{description}</TooltipContent>
<TooltipTrigger className="w-full overflow-hidden"> <TooltipTrigger className="w-full overflow-hidden">
<div className="max-w-[160px] overflow-hidden overflow-ellipsis whitespace-nowrap text-left text-[13px] font-normal text-white"> <div
className="max-w-[160px] overflow-hidden overflow-ellipsis whitespace-nowrap text-left text-[13px] font-normal text-white"
data-cy="series-description-label"
>
{description} {description}
</div> </div>
</TooltipTrigger> </TooltipTrigger>

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the angle tool', async ({ test('should display the angle tool', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -17,6 +18,6 @@ test('should display the angle tool', async ({
{ x: 450, y: 250 }, { x: 450, y: 250 },
{ x: 550, y: 300 }, { x: 550, y: 300 },
]); ]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot(page, page, screenShotPaths.angle.angleDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.angle.angleDisplayedCorrectly);
}); });

View File

@ -8,10 +8,12 @@ test.beforeEach(async ({ page }) => {
test('should display the arrow tool and allow free-form text to be entered', async ({ test('should display the arrow tool and allow free-form text to be entered', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
await page.getByTestId('trackedMeasurements-btn').click(); await rightPanelPageObject.measurementsPanel.select();
await mainToolbarPageObject.measurementTools.arrowAnnotate.click(); await mainToolbarPageObject.measurementTools.arrowAnnotate.click();
@ -20,10 +22,11 @@ test('should display the arrow tool and allow free-form text to be entered', asy
{ x: 344, y: 232 }, { x: 344, y: 232 },
]); ]);
await page.getByTestId('dialog-input').fill('Ringo Starr was the drummer for The Beatles'); await DOMOverlayPageObject.dialog.input.fillAndSave(
await page.getByTestId('input-dialog-save-button').click(); 'Ringo Starr was the drummer for The Beatles'
);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
@ -37,8 +40,7 @@ test('should display the arrow tool and allow free-form text to be entered', asy
await viewportPageObject.active.doubleClickAt({ x: 164, y: 234 }); await viewportPageObject.active.doubleClickAt({ x: 164, y: 234 });
await page.getByTestId('dialog-input').fill('Neil Peart was the drummer for Rush'); await DOMOverlayPageObject.dialog.input.fillAndSave('Neil Peart was the drummer for Rush');
await page.getByTestId('input-dialog-save-button').click();
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
@ -50,11 +52,9 @@ test('should display the arrow tool and allow free-form text to be entered', asy
// Now edit the label and the text should not change. // Now edit the label and the text should not change.
await page.getByTestId('actionsMenuTrigger').click(); await rightPanelPageObject.measurementsPanel.panel
await page.getByTestId('Rename').click(); .nthMeasurement(0)
.actions.rename('Drummer annotation arrow');
await page.getByTestId('dialog-input').fill('Drummer annotation arrow');
await page.getByTestId('input-dialog-save-button').click();
await page.waitForTimeout(2000); await page.waitForTimeout(2000);

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the bidirectional tool', async ({ test('should display the bidirectional tool', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -16,7 +17,7 @@ test('should display the bidirectional tool', async ({
{ x: 405, y: 277 }, { x: 405, y: 277 },
{ x: 515, y: 339 }, { x: 515, y: 339 },
]); ]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot( await checkForScreenshot(
page, page,
page, page,

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the circle tool', async ({ test('should display the circle tool', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -16,6 +17,6 @@ test('should display the circle tool', async ({
{ x: 480, y: 205 }, { x: 480, y: 205 },
{ x: 488, y: 247 }, { x: 488, y: 247 },
]); ]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot(page, page, screenShotPaths.circle.circleDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.circle.circleDisplayedCorrectly);
}); });

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the cobb angle tool', async ({ test('should display the cobb angle tool', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -18,6 +19,6 @@ test('should display the cobb angle tool', async ({
{ x: 527, y: 293 }, { x: 527, y: 293 },
{ x: 625, y: 291 }, { x: 625, y: 291 },
]); ]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot(page, page, screenShotPaths.cobbangle.cobbangleDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.cobbangle.cobbangleDisplayedCorrectly);
}); });

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should the context menu completely on screen and is not clipped for a point near the bottom edge of the screen', async ({ test('should the context menu completely on screen and is not clipped for a point near the bottom edge of the screen', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -17,7 +18,7 @@ test('should the context menu completely on screen and is not clipped for a poin
{ x: 0.55, y: 0.98 }, { x: 0.55, y: 0.98 },
]); ]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot(page, page, screenShotPaths.contextMenu.preContextMenuNearBottomEdge); await checkForScreenshot(page, page, screenShotPaths.contextMenu.preContextMenuNearBottomEdge);

View File

@ -5,17 +5,20 @@ const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501';
test('should not allow contours to be edited in basic viewer mode', async ({ test('should not allow contours to be edited in basic viewer mode', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
const mode = 'viewer'; const mode = 'viewer';
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
await page.getByTestId('side-panel-header-right').click(); await rightPanelPageObject.toggle();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
// Wait for the segmentation to be loaded. // Wait for the segmentation to be loaded.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for the segmentation to hydrate. // Wait for the segmentation to hydrate.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -46,13 +49,16 @@ test('should not allow contours to be edited in basic viewer mode', async ({
test('should not allow contours to be edited when panelSegmentation.disableEditing is true', async ({ test('should not allow contours to be edited when panelSegmentation.disableEditing is true', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
const mode = 'segmentation'; const mode = 'segmentation';
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
await page.getByTestId('side-panel-header-right').click(); await rightPanelPageObject.toggle();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
// Wait for the segmentation to be loaded. // Wait for the segmentation to be loaded.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -66,7 +72,7 @@ test('should not allow contours to be edited when panelSegmentation.disableEditi
); );
}); });
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for the segmentation to hydrate. // Wait for the segmentation to hydrate.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -97,13 +103,16 @@ test('should not allow contours to be edited when panelSegmentation.disableEditi
test('should allow contours to be edited when panelSegmentation.disableEditing is false', async ({ test('should allow contours to be edited when panelSegmentation.disableEditing is false', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
const mode = 'segmentation'; const mode = 'segmentation';
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
await page.getByTestId('side-panel-header-right').click(); await rightPanelPageObject.toggle();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
// Wait for the segmentation to be loaded. // Wait for the segmentation to be loaded.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -117,7 +126,7 @@ test('should allow contours to be edited when panelSegmentation.disableEditing i
); );
}); });
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for the segmentation to hydrate. // Wait for the segmentation to hydrate.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -1,44 +1,11 @@
import { Page } from '@playwright/test';
import { import {
checkForScreenshot, checkForScreenshot,
getMousePosition,
initializeMousePositionTracker, initializeMousePositionTracker,
screenShotPaths, screenShotPaths,
test, test,
visitStudy, visitStudy,
} from './utils/index.js'; } from './utils/index.js';
const rotateCrosshairs = async (page: Page, id: string, lineNumber: number) => {
const locator = page.locator(id).locator('line').nth(lineNumber);
await locator.click({ force: true });
await locator.hover({ force: true });
const circleLocator = page.locator(id).locator('circle').nth(1);
await circleLocator.hover({ force: true });
await page.mouse.down();
const position = await getMousePosition(page);
await page.mouse.move(position.x, position.y + 100);
await page.mouse.up();
};
const increaseSlabThickness = async (page: Page, id: string, lineNumber: number, axis: string) => {
const locator = page.locator(id).locator('line').nth(lineNumber);
await locator.click({ force: true });
await locator.hover({ force: true });
const circleLocator = page.locator(id).locator('rect').first();
await circleLocator.hover({ force: true });
await page.mouse.down();
const position = await getMousePosition(page);
switch (axis) {
case 'x':
await page.mouse.move(position.x + 100, position.y);
break;
case 'y':
await page.mouse.move(position.x, position.y + 100);
break;
}
await page.mouse.up();
};
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.1706.8374.643249677828306008300337414785'; const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.1706.8374.643249677828306008300337414785';
const mode = 'viewer'; const mode = 'viewer';
@ -49,7 +16,7 @@ test.beforeEach(async ({ page }) => {
test.describe('Crosshairs Test', async () => { test.describe('Crosshairs Test', async () => {
test('should render the crosshairs correctly.', async ({ page, mainToolbarPageObject }) => { test('should render the crosshairs correctly.', async ({ page, mainToolbarPageObject }) => {
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
await page.getByTestId('Crosshairs').click(); await mainToolbarPageObject.crosshairs.click();
await checkForScreenshot(page, page, screenShotPaths.crosshairs.crosshairsRendered); await checkForScreenshot(page, page, screenShotPaths.crosshairs.crosshairsRendered);
}); });
@ -57,13 +24,14 @@ test.describe('Crosshairs Test', async () => {
test('should allow the user to rotate the crosshairs', async ({ test('should allow the user to rotate the crosshairs', async ({
page, page,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject,
}) => { }) => {
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
await page.getByTestId('Crosshairs').click(); await mainToolbarPageObject.crosshairs.click();
await rotateCrosshairs(page, '#svg-layer-mpr-axial', 3); await viewportPageObject.crosshairs.axial.rotate();
await rotateCrosshairs(page, '#svg-layer-mpr-sagittal', 0); await viewportPageObject.crosshairs.sagittal.rotate();
await rotateCrosshairs(page, '#svg-layer-mpr-coronal', 0); await viewportPageObject.crosshairs.coronal.rotate();
await checkForScreenshot(page, page, screenShotPaths.crosshairs.crosshairsRotated); await checkForScreenshot(page, page, screenShotPaths.crosshairs.crosshairsRotated);
}); });
@ -71,13 +39,14 @@ test.describe('Crosshairs Test', async () => {
test('should allow the user to adjust the slab thickness', async ({ test('should allow the user to adjust the slab thickness', async ({
page, page,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject,
}) => { }) => {
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
await page.getByTestId('Crosshairs').click(); await mainToolbarPageObject.crosshairs.click();
await increaseSlabThickness(page, '#svg-layer-mpr-axial', 0, 'x'); await viewportPageObject.crosshairs.axial.increase();
await increaseSlabThickness(page, '#svg-layer-mpr-sagittal', 2, 'x'); await viewportPageObject.crosshairs.sagittal.increase();
await increaseSlabThickness(page, '#svg-layer-mpr-coronal', 0, 'y'); await viewportPageObject.crosshairs.coronal.increase();
await checkForScreenshot(page, page, screenShotPaths.crosshairs.crosshairsSlabThickness); await checkForScreenshot(page, page, screenShotPaths.crosshairs.crosshairsSlabThickness);
}); });
@ -85,15 +54,16 @@ test.describe('Crosshairs Test', async () => {
test('should reset the crosshairs to the initial position when reset is clicked', async ({ test('should reset the crosshairs to the initial position when reset is clicked', async ({
page, page,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject,
}) => { }) => {
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
await page.getByTestId('Crosshairs').click(); await mainToolbarPageObject.crosshairs.click();
await rotateCrosshairs(page, '#svg-layer-mpr-axial', 3); await viewportPageObject.crosshairs.axial.rotate();
await rotateCrosshairs(page, '#svg-layer-mpr-sagittal', 0); await viewportPageObject.crosshairs.sagittal.rotate();
await rotateCrosshairs(page, '#svg-layer-mpr-coronal', 0); await viewportPageObject.crosshairs.coronal.rotate();
await page.getByTestId('MoreTools-split-button-primary').click(); await mainToolbarPageObject.moreTools.reset.click();
await checkForScreenshot(page, page, screenShotPaths.crosshairs.crosshairsResetToolbar); await checkForScreenshot(page, page, screenShotPaths.crosshairs.crosshairsResetToolbar);
}); });
@ -101,15 +71,17 @@ test.describe('Crosshairs Test', async () => {
test('should reset the crosshairs when a new displayset is loaded', async ({ test('should reset the crosshairs when a new displayset is loaded', async ({
page, page,
mainToolbarPageObject, mainToolbarPageObject,
leftPanelPageObject,
viewportPageObject,
}) => { }) => {
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
await page.getByTestId('Crosshairs').click(); await mainToolbarPageObject.crosshairs.click();
await rotateCrosshairs(page, '#svg-layer-mpr-axial', 0); await viewportPageObject.crosshairs.axial.rotate();
await rotateCrosshairs(page, '#svg-layer-mpr-sagittal', 0); await viewportPageObject.crosshairs.sagittal.rotate();
await rotateCrosshairs(page, '#svg-layer-mpr-coronal', 3); await viewportPageObject.crosshairs.coronal.rotate();
await page.getByTestId('study-browser-thumbnail').nth(1).dblclick(); await leftPanelPageObject.loadSeriesByDescription('Recon 3: LIVER 3 PHASE (AP)');
await checkForScreenshot(page, page, screenShotPaths.crosshairs.crosshairsNewDisplayset); await checkForScreenshot(page, page, screenShotPaths.crosshairs.crosshairsNewDisplayset);
}); });

View File

@ -10,10 +10,9 @@ test('should display added, selected and removed segmentation promptly', async (
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
// Add a segmentation overlay and ensure the overlay menu reflects this change. // Add a segmentation overlay and ensure the overlay menu reflects this change.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); const dataOverlayPageObject = viewportPageObject.getById('default').overlayMenu.dataOverlay;
await page.getByTestId('AddSegmentationDataOverlay-default').click(); await dataOverlayPageObject.toggle();
await page.getByText('SELECT A SEGMENTATION').click(); await dataOverlayPageObject.addSegmentation('2d-tta_nnU-Net_Segmentation');
await page.getByTestId('2d-tta_nnU-Net_Segmentation').click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -24,7 +23,7 @@ test('should display added, selected and removed segmentation promptly', async (
}); });
// Hide the overlay menu. // Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await dataOverlayPageObject.toggle();
// navigate to the 51st image and ensure the correct overlay is displayed // navigate to the 51st image and ensure the correct overlay is displayed
await press({ page, key: 'ArrowDown', nTimes: 50 }); await press({ page, key: 'ArrowDown', nTimes: 50 });
@ -37,11 +36,10 @@ test('should display added, selected and removed segmentation promptly', async (
}); });
// Show the overlay menu. // Show the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await dataOverlayPageObject.toggle();
// Change the segmentation overlay to a different one and ensure the overlay menu reflects this change. // Change the segmentation overlay to a different one and ensure the overlay menu reflects this change.
await page.getByTestId('overlay-ds-select-value-2D-TTA_NNU-NET_SEGMENTATION').click(); await dataOverlayPageObject.changeSegmentation('2d-tta_nnU-Net_Segmentation', 'Segmentation');
await page.getByTestId('Segmentation-SEG').click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -51,7 +49,7 @@ test('should display added, selected and removed segmentation promptly', async (
}); });
// Hide the overlay menu. // Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await dataOverlayPageObject.toggle();
// navigate to the 51st image and ensure the correct overlay is displayed // navigate to the 51st image and ensure the correct overlay is displayed
await press({ page, key: 'ArrowDown', nTimes: 50 }); await press({ page, key: 'ArrowDown', nTimes: 50 });
@ -64,11 +62,10 @@ test('should display added, selected and removed segmentation promptly', async (
}); });
// Show the overlay menu. // Show the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await dataOverlayPageObject.toggle();
// Remove the segmentation overlay and ensure the overlay menu reflects this change. // Remove the segmentation overlay and ensure the overlay menu reflects this change.
await page.getByTestId('overlay-ds-more-button-SEGMENTATION').click(); await dataOverlayPageObject.remove('SEGMENTATION');
await page.getByTestId('overlay-ds-remove-button-SEGMENTATION').click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -78,7 +75,7 @@ test('should display added, selected and removed segmentation promptly', async (
}); });
// Hide the overlay menu. // Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await dataOverlayPageObject.toggle();
// navigate to the 51st image and ensure no overlay is displayed // navigate to the 51st image and ensure no overlay is displayed
await press({ page, key: 'ArrowDown', nTimes: 50 }); await press({ page, key: 'ArrowDown', nTimes: 50 });

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the ellipse tool', async ({ test('should display the ellipse tool', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -16,6 +17,6 @@ test('should display the ellipse tool', async ({
{ x: 446, y: 245 }, { x: 446, y: 245 },
{ x: 508, y: 281 }, { x: 508, y: 281 },
]); ]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot(page, page, screenShotPaths.ellipse.ellipseDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.ellipse.ellipseDisplayedCorrectly);
}); });

View File

@ -8,15 +8,16 @@ test.beforeEach(async ({ page }) => {
test('should hydrate in MPR correctly', async ({ test('should hydrate in MPR correctly', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
mainToolbarPageObject, mainToolbarPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
await page.getByTestId('side-panel-header-right').click(); await rightPanelPageObject.toggle();
await page.getByTestId('trackedMeasurements-btn').click(); await rightPanelPageObject.measurementsPanel.select();
// get the div that has Body 4.0 Lung I and double click it await leftPanelPageObject.loadSeriesByDescription('Body 4.0 CE', 1);
await page.locator(':text("S:7")').first().dblclick();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -47,10 +48,9 @@ test('should hydrate in MPR correctly', async ({
{ x: 515, y: 339 }, { x: 515, y: 339 },
]); ]);
// wait 2 seconds
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
// scroll away // scroll away
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.initialDraw); await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.initialDraw);
@ -75,34 +75,32 @@ test('should hydrate in MPR correctly', async ({
} }
}); });
// wait 5 seconds
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.scrollAway); await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.scrollAway);
await page.getByTestId('data-row').first().click(); await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.jumpToMeasurementStack); await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.jumpToMeasurementStack);
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
// wait 5 seconds
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
// jump in viewport again // jump in viewport again
await page.getByTestId('data-row').first().click(); await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.jumpInMPR); await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.jumpInMPR);
await page.locator(':text("S:3")').first().dblclick(); await leftPanelPageObject.loadSeriesByDescription('Lung 3.0 CE');
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.changeSeriesInMPR); await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.changeSeriesInMPR);
await page.getByTestId('data-row').first().click(); await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
await checkForScreenshot( await checkForScreenshot(
page, page,

View File

@ -9,6 +9,9 @@ test.beforeEach(async ({ page }) => {
test('should prevent editing of label map segmentations when panelSegmentation.disableEditing is true', async ({ test('should prevent editing of label map segmentations when panelSegmentation.disableEditing is true', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
// disable editing of segmentations via the customization service // disable editing of segmentations via the customization service
@ -20,13 +23,13 @@ test('should prevent editing of label map segmentations when panelSegmentation.d
} }
); );
}); });
await page.getByTestId('panelSegmentationWithToolsLabelMap-btn').click(); await rightPanelPageObject.labelMapSegmentationPanel.select();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByModality('SEG');
// Wait for the segmentation to be loaded. // Wait for the segmentation to be loaded.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for the segmentation to hydrate. // Wait for the segmentation to hydrate.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -37,10 +40,10 @@ test('should prevent editing of label map segmentations when panelSegmentation.d
await checkForScreenshot(page, page, screenShotPaths.labelMapSegLocking.globalLockedSegPreEdit); await checkForScreenshot(page, page, screenShotPaths.labelMapSegLocking.globalLockedSegPreEdit);
// Attempt to erase the segmentations. // Attempt to erase the segmentations.
await page.getByTestId('Eraser-btn').click(); await rightPanelPageObject.labelMapSegmentationPanel.tools.eraser.click();
// Use the largest eraser radius to help ensure the entire image is erased. // Use the largest eraser radius to help ensure the entire image is erased.
await page.locator(`css=div[data-cy="eraser-radius"] input`).fill('1000'); await rightPanelPageObject.labelMapSegmentationPanel.tools.eraser.setRadius(1000);
// Attempt to erase the segmentations by dragging the eraser tool across the image several times. // Attempt to erase the segmentations by dragging the eraser tool across the image several times.
await viewportPageObject.getById('default').normalizedDragAt({ await viewportPageObject.getById('default').normalizedDragAt({
@ -61,6 +64,9 @@ test('should prevent editing of label map segmentations when panelSegmentation.d
test('should allow editing of label map segmentations when panelSegmentation.disableEditing is false', async ({ test('should allow editing of label map segmentations when panelSegmentation.disableEditing is false', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
// disable editing of segmentations via the customization service // disable editing of segmentations via the customization service
@ -73,13 +79,13 @@ test('should allow editing of label map segmentations when panelSegmentation.dis
); );
}); });
await page.getByTestId('panelSegmentationWithToolsLabelMap-btn').click(); await rightPanelPageObject.labelMapSegmentationPanel.select();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByModality('SEG');
// Wait for the segmentation to be loaded. // Wait for the segmentation to be loaded.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for the segmentation to hydrate. // Wait for the segmentation to hydrate.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -89,10 +95,10 @@ test('should allow editing of label map segmentations when panelSegmentation.dis
await checkForScreenshot(page, page, screenShotPaths.labelMapSegLocking.globalUnlockedSegPreEdit); await checkForScreenshot(page, page, screenShotPaths.labelMapSegLocking.globalUnlockedSegPreEdit);
// Attempt to erase the segmentations. // Attempt to erase the segmentations.
await page.getByTestId('Eraser-btn').click(); await rightPanelPageObject.labelMapSegmentationPanel.tools.eraser.click();
// Use the largest eraser radius to help ensure the eraser passes over the entire image. // Use the largest eraser radius to help ensure the eraser passes over the entire image.
await page.locator(`css=div[data-cy="eraser-radius"] input`).fill('1000'); await rightPanelPageObject.labelMapSegmentationPanel.tools.eraser.setRadius(1000);
// Attempt to erase the segmentations by dragging the eraser tool across the image several times. // Attempt to erase the segmentations by dragging the eraser tool across the image several times.
await viewportPageObject.getById('default').normalizedDragAt({ await viewportPageObject.getById('default').normalizedDragAt({

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the length tool', async ({ test('should display the length tool', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -16,6 +17,6 @@ test('should display the length tool', async ({
{ x: 364, y: 234 }, { x: 364, y: 234 },
{ x: 544, y: 232 }, { x: 544, y: 232 },
]); ]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot(page, page, screenShotPaths.length.lengthDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.length.lengthDisplayedCorrectly);
}); });

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the livewire tool', async ({ test('should display the livewire tool', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -20,6 +21,6 @@ test('should display the livewire tool', async ({
{ x: 482, y: 493 }, { x: 482, y: 493 },
{ x: 383, y: 461 }, { x: 383, y: 461 },
]); ]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot(page, page, screenShotPaths.livewire.livewireDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.livewire.livewireDisplayedCorrectly);
}); });

View File

@ -25,16 +25,15 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me
// Hover over the middle/sagittal viewport so that the data overlay menu is available. // Hover over the middle/sagittal viewport so that the data overlay menu is available.
await viewportPageObject.getById('mpr-sagittal').pane.hover(); await viewportPageObject.getById('mpr-sagittal').pane.hover();
await viewportPageObject.getById('mpr-sagittal').overlayMenu.dataOverlay.click(); const dataOverlayPageObject = viewportPageObject.getById('mpr-sagittal').overlayMenu.dataOverlay;
await page.getByTestId('AddSegmentationDataOverlay-mpr-sagittal').click(); await dataOverlayPageObject.toggle('mpr-sagittal');
await page.getByText('SELECT A SEGMENTATION').click(); await dataOverlayPageObject.addSegmentation('ARIA RadOnc Structure Sets', 'mpr-sagittal');
await page.getByTestId('ARIA RadOnc Structure Sets').click();
// Hide the overlay menu. // Hide the overlay menu.
await viewportPageObject.getById('mpr-sagittal').overlayMenu.dataOverlay.click(); await dataOverlayPageObject.toggle('mpr-sagittal');
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); await assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
// Wait 5 seconds for RT to load. This is necessary in particular when screen shots are added or replaced. // Wait 5 seconds for RT to load. This is necessary in particular when screen shots are added or replaced.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -9,10 +9,11 @@ test.beforeEach(async ({ page }) => {
test('should launch MPR with unhydrated SEG chosen from the data overlay menu', async ({ test('should launch MPR with unhydrated SEG chosen from the data overlay menu', async ({
page, page,
rightPanelPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
await page.getByTestId('side-panel-header-right').click(); await rightPanelPageObject.toggle();
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
@ -25,17 +26,16 @@ test('should launch MPR with unhydrated SEG chosen from the data overlay menu',
); );
// Hover over the middle/sagittal viewport so that the data overlay menu is available. // Hover over the middle/sagittal viewport so that the data overlay menu is available.
await viewportPageObject.getById('mpr-sagittal').pane.hover(); await viewportPageObject.getById('mpr-axial').pane.hover();
await viewportPageObject.getById('mpr-sagittal').overlayMenu.dataOverlay.click(); const dataOverlayPageObject = viewportPageObject.getById('mpr-axial').overlayMenu.dataOverlay;
await page.getByTestId('AddSegmentationDataOverlay-mpr-sagittal').click(); await dataOverlayPageObject.toggle('mpr-axial');
await page.getByText('SELECT A SEGMENTATION').click(); await dataOverlayPageObject.addSegmentation('Segmentation', 'mpr-axial');
await page.getByTestId('Segmentation').click();
// Hide the overlay menu. // Hide the overlay menu.
await viewportPageObject.getById('mpr-sagittal').overlayMenu.dataOverlay.click(); await dataOverlayPageObject.toggle('mpr-axial');
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); await assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -7,136 +7,114 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('checks if Measurements right panel can be hidden/displayed', async ({ page }) => { test('checks if Measurements right panel can be hidden/displayed', async ({
const measurementsPanel = page.getByTestId('trackedMeasurements-panel').last(); rightPanelPageObject,
const rightCollapseBtn = page.getByTestId('side-panel-header-right'); }) => {
const measurementsPanel = rightPanelPageObject.measurementsPanel.panel.locator;
// No data-cy exists in this panel, using Segmentation header button const segmentationPanel = rightPanelPageObject.labelMapSegmentationPanel.panel.locator;
const segmentationPanel = page.getByRole('button', { name: 'Segmentations' });
const measurementsBtn = page.getByTestId('trackedMeasurements-btn');
const segmentationsBtn = page.getByTestId('panelSegmentation-btn');
// Assert the measurements panel and segmentation panel is hidden initially // Assert the measurements panel and segmentation panel is hidden initially
await expect(measurementsPanel).toBeHidden(); await expect(measurementsPanel).toBeHidden();
await expect(segmentationPanel).toBeHidden(); await expect(segmentationPanel).toBeHidden();
// Click the collapse button to show the panel container // Click the collapse button to show the panel container
await rightCollapseBtn.click({ force: true }); await rightPanelPageObject.toggle();
// The segmentation panel should now be visible by default // The segmentation panel should now be visible by default
await expect(segmentationPanel).toBeVisible(); await expect(segmentationPanel).toBeVisible();
// Switch to the measurements tab // Switch to the measurements tab
await measurementsBtn.click(); await rightPanelPageObject.measurementsPanel.select();
// Assert the measurements panel is visible, and segmentation invisible // Assert the measurements panel is visible, and segmentation invisible
await expect(measurementsPanel).toBeVisible(); await expect(measurementsPanel).toBeVisible();
await expect(segmentationPanel).toBeHidden(); await expect(segmentationPanel).toBeHidden();
// Switch back to segmentations panel // Switch back to segmentations panel
await segmentationsBtn.click(); await rightPanelPageObject.noToolsSegmentationPanel.select();
// Assert the segmentations panel is now visible, measurements panel invisible // Assert the segmentations panel is now visible, measurements panel invisible
await expect(segmentationPanel).toBeVisible(); await expect(segmentationPanel).toBeVisible();
await expect(measurementsPanel).toBeHidden(); await expect(measurementsPanel).toBeHidden();
// Click the collapse button to hide the panel // Click the collapse button to hide the panel
await rightCollapseBtn.click(); await rightPanelPageObject.toggle();
// Assert the measurements and segmentation panel is now hidden // Assert the measurements and segmentation panel is now hidden
await expect(measurementsPanel).toBeHidden(); await expect(measurementsPanel).toBeHidden();
await expect(segmentationPanel).toBeHidden(); await expect(segmentationPanel).toBeHidden();
}); });
test('checks if measurement item can be relabeled under Measurements panel', async ({ page }) => { test('checks if measurement item can be relabeled under Measurements panel', async ({
page,
DOMOverlayPageObject,
rightPanelPageObject,
}) => {
const relabelText = 'Relabel 12345'; const relabelText = 'Relabel 12345';
const measurementsBtn = page.getByTestId('trackedMeasurements-btn');
// Add measurement // Add measurement
await addLengthMeasurement(page); await addLengthMeasurement(page);
const viewportNotification = page.getByTestId('viewport-notification'); const viewportNotification = DOMOverlayPageObject.viewport.measurementTracking.locator;
await expect(viewportNotification).toBeVisible(); await expect(viewportNotification).toBeVisible();
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
// Open measurement panel confirm default empty // Open measurement panel confirm default empty
await measurementsBtn.click(); await rightPanelPageObject.measurementsPanel.select();
const measurementRow = page.getByTestId('data-row').first(); const measurementRow = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).locator;
await expect(measurementRow).toContainText('(empty)'); await expect(measurementRow).toContainText('(empty)');
// Expand and click rename // Expand and click rename
const actionsButton = measurementRow.getByRole('button', { name: 'Actions' }); await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).actions.rename(relabelText);
await actionsButton.click();
const renameButton = page.getByRole('menuitem', { name: 'Rename' });
await renameButton.click();
// Interact with dialog
const renameDialog = page.getByRole('dialog', { name: 'Edit Measurement Label' });
const renameInput = renameDialog.getByPlaceholder('Enter new label');
const saveButton = renameDialog.getByRole('button', { name: 'Save' });
await expect(renameDialog).toBeVisible();
await expect(renameInput).toBeVisible();
await expect(saveButton).toBeEnabled();
await renameInput.fill(relabelText);
await saveButton.click();
// Check dialog closed and renamed // Check dialog closed and renamed
await expect(renameDialog).toBeHidden(); await expect(DOMOverlayPageObject.dialog.input.locator).toBeHidden();
await expect(measurementRow).toContainText(relabelText); await expect(measurementRow).toContainText(relabelText);
}); });
test('checks if measurement item can be relabeled through the context menu on the viewport', async ({ test('checks if measurement item can be relabeled through the context menu on the viewport', async ({
page, page,
DOMOverlayPageObject,
rightPanelPageObject,
viewportPageObject,
}) => { }) => {
const relabelText = 'Relabel 12345'; const relabelText = 'Relabel 12345';
const measurementsBtn = page.getByTestId('trackedMeasurements-btn');
// Add measurement // Add measurement
await addLengthMeasurement(page); await addLengthMeasurement(page);
const viewportNotification = page.getByTestId('viewport-notification'); const viewportNotification = DOMOverlayPageObject.viewport.measurementTracking.locator;
await expect(viewportNotification).toBeVisible(); await expect(viewportNotification).toBeVisible();
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
// Open measurement panel confirm default empty // Open measurement panel confirm default empty
await measurementsBtn.click(); await rightPanelPageObject.measurementsPanel.select();
const measurementRow = page.getByTestId('data-row').first(); const measurementRow = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).locator;
await expect(measurementRow).toContainText('(empty)'); await expect(measurementRow).toContainText('(empty)');
// Right click and click rename // Right click and click rename
await page.waitForTimeout(200); // small delay for context menu await page.waitForTimeout(200); // small delay for context menu
const measurementAnnotation = page.locator('g[data-annotation-uid]').first(); await viewportPageObject.active.nthAnnotation(0).contextMenu.open();
await measurementAnnotation.click({ button: 'right', force: true });
await page.waitForTimeout(200); // small delay for context menu await page.waitForTimeout(200); // small delay for context menu
const addLabelButton = page.getByTestId('context-menu-item').filter({ hasText: 'Add Label' }); const addLabelButton = DOMOverlayPageObject.viewport.annotationContextMenu.addLabel;
await expect(addLabelButton).toBeVisible(); await expect(addLabelButton.locator).toBeVisible();
await addLabelButton.click(); await addLabelButton.click();
// Interact with dialog // Interact with dialog
const renameDialog = page.getByRole('dialog', { name: 'Edit Measurement Label' }); await expect(DOMOverlayPageObject.dialog.title).toHaveText('Edit Measurement Label');
const renameInput = renameDialog.getByPlaceholder('Enter new label'); await DOMOverlayPageObject.dialog.input.fillAndSave(relabelText);
const saveButton = renameDialog.getByRole('button', { name: 'Save' });
await expect(renameDialog).toBeVisible();
await expect(renameInput).toBeVisible();
await expect(saveButton).toBeEnabled();
await renameInput.fill(relabelText);
await saveButton.click();
// Check dialog closed and renamed // Check dialog closed and renamed
await expect(renameDialog).toBeHidden(); await expect(DOMOverlayPageObject.dialog.title).toBeHidden();
await expect(measurementRow).toContainText(relabelText); await expect(measurementRow).toContainText(relabelText);
}); });
test('checks if image would jump when clicked on a measurement item', async ({ test('checks if image would jump when clicked on a measurement item', async ({
page, page,
DOMOverlayPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
const viewportInfoBottomRight = viewportPageObject.active.overlayText.bottomRight; const viewportInfoBottomRight = viewportPageObject.active.overlayText.bottomRight;
@ -145,9 +123,8 @@ test('checks if image would jump when clicked on a measurement item', async ({
await expect(viewportInfoBottomRight).toContainText('1/', { timeout: 10000 }); await expect(viewportInfoBottomRight).toContainText('1/', { timeout: 10000 });
await addLengthMeasurement(page); await addLengthMeasurement(page);
const viewportNotification = page.getByTestId('viewport-notification'); await expect(DOMOverlayPageObject.viewport.measurementTracking.locator).toBeVisible();
await expect(viewportNotification).toBeVisible(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await page.getByTestId('prompt-begin-tracking-yes-btn').click();
// Change to slice 2 // Change to slice 2
await scrollVolumeViewport(page, 'default', 1); await scrollVolumeViewport(page, 'default', 1);
@ -157,90 +134,79 @@ test('checks if image would jump when clicked on a measurement item', async ({
await addLengthMeasurement(page); await addLengthMeasurement(page);
// Open measurement panel and click first measurement // Open measurement panel and click first measurement
const measurementsBtn = page.getByTestId('trackedMeasurements-btn'); await rightPanelPageObject.measurementsPanel.select();
await measurementsBtn.click(); await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
const measurementRow = page.getByTestId('data-row').first();
await measurementRow.click();
// Confirm jumped to slice 1 // Confirm jumped to slice 1
await expect(viewportInfoBottomRight).toContainText('1/', { timeout: 10000 }); await expect(viewportInfoBottomRight).toContainText('1/', { timeout: 10000 });
await expect(viewportInfoBottomRight).not.toContainText('2/'); await expect(viewportInfoBottomRight).not.toContainText('2/');
}); });
test('checks if measurement item can be deleted under Measurements panel', async ({ page }) => { test('checks if measurement item can be deleted under Measurements panel', async ({
const measurementsBtn = page.getByTestId('trackedMeasurements-btn'); page,
DOMOverlayPageObject,
rightPanelPageObject,
}) => {
// Add 3 measurements // Add 3 measurements
await addLengthMeasurement(page); await addLengthMeasurement(page);
const viewportNotification = page.getByTestId('viewport-notification'); await expect(DOMOverlayPageObject.viewport.measurementTracking.locator).toBeVisible();
await expect(viewportNotification).toBeVisible(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await page.getByTestId('prompt-begin-tracking-yes-btn').click();
await addLengthMeasurement(page, { firstClick: [170, 100], secondClick: [150, 170] }); await addLengthMeasurement(page, { firstClick: [170, 100], secondClick: [150, 170] });
await addLengthMeasurement(page, { firstClick: [190, 100], secondClick: [170, 170] }); await addLengthMeasurement(page, { firstClick: [190, 100], secondClick: [170, 170] });
// Open measurement panel, confirm 3 measurements // Open measurement panel, confirm 3 measurements
await measurementsBtn.click(); await rightPanelPageObject.measurementsPanel.select();
await expect(page.getByTestId('data-row')).toHaveCount(3); expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(3);
// Delete from measurement // Delete from measurement
const measurementRow = page.getByTestId('data-row').first(); await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).actions.delete();
const actionsButton = measurementRow.getByRole('button', { name: 'Actions' }); await page.waitForTimeout(200);
await actionsButton.click();
const menuDeleteButton = page.getByRole('menuitem', { name: 'Delete' });
await menuDeleteButton.click();
// Confirm one measurement is gone // Confirm one measurement is gone
await expect(page.getByTestId('data-row')).toHaveCount(2); expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(2);
// Delete all measurements via main Measurement Panel delete button and untrack // Delete all measurements via main Measurement Panel delete button and untrack
const deleteButton = page.getByRole('button', { name: 'Delete' }); await rightPanelPageObject.measurementsPanel.panel.deleteAll();
deleteButton.click();
// Interact with dialog // Interact with dialog
const untrackDialog = page.getByRole('dialog', { name: 'Untrack Study' }); await expect(DOMOverlayPageObject.dialog.title).toHaveText('Untrack Study');
const untrackButton = untrackDialog.getByRole('button', { name: 'Untrack' });
await expect(untrackDialog).toBeVisible(); await expect(DOMOverlayPageObject.dialog.confirmation.confirm.button).toBeEnabled();
await expect(untrackButton).toBeEnabled();
await untrackButton.click(); await DOMOverlayPageObject.dialog.confirmation.confirm.click();
// Check dialog closed and measurements gone // Check dialog closed and measurements gone
await expect(untrackDialog).toBeHidden(); await expect(DOMOverlayPageObject.dialog.title).toBeHidden();
await expect(page.getByTestId('data-row')).toHaveCount(0); expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(0);
const measurementsPanel = page.getByTestId('trackedMeasurements-panel').last(); const measurementsPanel = rightPanelPageObject.measurementsPanel.panel.locator;
await expect(measurementsPanel).toContainText('No tracked measurements'); await expect(measurementsPanel).toContainText('No tracked measurements');
}); });
test('checks if measurement item can be deleted through the context menu on the viewport', async ({ test('checks if measurement item can be deleted through the context menu on the viewport', async ({
page, page,
DOMOverlayPageObject,
rightPanelPageObject,
viewportPageObject,
}) => { }) => {
const measurementsBtn = page.getByTestId('trackedMeasurements-btn');
// Add measurement // Add measurement
await addLengthMeasurement(page); await addLengthMeasurement(page);
await expect(DOMOverlayPageObject.viewport.measurementTracking.locator).toBeVisible();
const viewportNotification = page.getByTestId('viewport-notification'); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await expect(viewportNotification).toBeVisible();
await page.getByTestId('prompt-begin-tracking-yes-btn').click();
// Right click and click rename // Right click and click rename
await page.waitForTimeout(200); // small delay for context menu await page.waitForTimeout(200); // small delay for context menu
const measurementAnnotation = page.locator('g[data-annotation-uid]').first(); await viewportPageObject.active.nthAnnotation(0).contextMenu.open();
await measurementAnnotation.click({ button: 'right', force: true });
await page.waitForTimeout(200); // small delay for context menu await page.waitForTimeout(200); // small delay for context menu
const deleteButton = page.getByTestId('context-menu-item').filter({ hasText: 'Delete' }); const deleteButton = DOMOverlayPageObject.viewport.annotationContextMenu.delete;
await expect(deleteButton).toBeVisible(); await expect(deleteButton.locator).toBeVisible();
await deleteButton.click(); await deleteButton.click();
// Open measurement panel and confirm measurement is gone // Open measurement panel and confirm measurement is gone
await measurementsBtn.click(); await rightPanelPageObject.measurementsPanel.select();
await expect(measurementAnnotation).toBeHidden(); await expect(viewportPageObject.active.nthAnnotation(0).locator).toBeHidden();
await expect(page.getByTestId('data-row')).toHaveCount(0); expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(0);
}); });

View File

@ -10,16 +10,15 @@ test.beforeEach(async ({ page }) => {
test('should display multiple segmentation overlays (both SEG and RT)', async ({ test('should display multiple segmentation overlays (both SEG and RT)', async ({
page, page,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
await page.getByTestId('side-panel-header-right').click(); await rightPanelPageObject.toggle();
// Add multiple segmentation overlays and ensure the overlay menu reflects this change. // Add multiple segmentation overlays and ensure the overlay menu reflects this change.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); const dataOverlayPageObject = viewportPageObject.getById('default').overlayMenu.dataOverlay;
await dataOverlayPageObject.toggle();
await page.getByTestId('AddSegmentationDataOverlay-default').click(); await dataOverlayPageObject.addSegmentation('2d-tta_nnU-Net_Segmentation');
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('2d-tta_nnU-Net_Segmentation').click();
// A short wait after each overlay is selected to ensure it loads. // A short wait after each overlay is selected to ensure it loads.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -27,9 +26,7 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
await page.getByTestId('AddSegmentationDataOverlay-default').click(); await dataOverlayPageObject.addSegmentation('Segmentation');
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('Segmentation').click();
// A short wait after each overlay is selected to ensure it loads. // A short wait after each overlay is selected to ensure it loads.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -37,9 +34,7 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
await page.getByTestId('AddSegmentationDataOverlay-default').click(); await dataOverlayPageObject.addSegmentation('3d_lowres-tta_nnU-Net_Segmentation');
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('3d_lowres-tta_nnU-Net_Segmentation').click();
// A short wait after each overlay is selected to ensure it loads. // A short wait after each overlay is selected to ensure it loads.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -53,15 +48,15 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
}); });
// Hide the overlay menu and then show it again. The overlays from before should still be displayed. // Hide the overlay menu and then show it again. The overlays from before should still be displayed.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); // hide await dataOverlayPageObject.toggle(); // hide
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); // show await dataOverlayPageObject.toggle(); // show
await checkForScreenshot({ await checkForScreenshot({
page, page,
screenshotPath: screenShotPaths.multipleSegmentationDataOverlays.threeSegOverlaysInOverlayMenu, screenshotPath: screenShotPaths.multipleSegmentationDataOverlays.threeSegOverlaysInOverlayMenu,
}); });
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); // hide await dataOverlayPageObject.toggle(); // hide
// Navigate to image 56. // Navigate to image 56.
await press({ page, key: 'ArrowDown', nTimes: 55 }); await press({ page, key: 'ArrowDown', nTimes: 55 });
@ -74,11 +69,9 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
}); });
// Now add the RT overlay // Now add the RT overlay
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await dataOverlayPageObject.toggle();
await page.getByTestId('AddSegmentationDataOverlay-default').click(); await dataOverlayPageObject.addSegmentation('Series 3 - RTSTRUCT');
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('Series 3 - RTSTRUCT').click();
// A short wait after each overlay is selected to ensure it loads. // A short wait after each overlay is selected to ensure it loads.
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -92,8 +85,8 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
}); });
// Hide the overlay menu and then show it again. The overlays from before should still be displayed. // Hide the overlay menu and then show it again. The overlays from before should still be displayed.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); // hide await dataOverlayPageObject.toggle(); // hide
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); // show await dataOverlayPageObject.toggle(); // show
await checkForScreenshot({ await checkForScreenshot({
page, page,

View File

@ -8,11 +8,12 @@ test.beforeEach(async ({ page }) => {
test('should display the probe tool', async ({ test('should display the probe tool', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
await mainToolbarPageObject.moreTools.probe.click(); await mainToolbarPageObject.moreTools.probe.click();
await viewportPageObject.active.clickAt([{ x: 550, y: 200 }]); await viewportPageObject.active.clickAt([{ x: 550, y: 200 }]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot(page, page, screenShotPaths.probe.probeDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.probe.probeDisplayedCorrectly);
}); });

View File

@ -12,16 +12,15 @@ test('should overlay an unhydrated RTSTRUCT over a display set that the RTSTRUCT
page, page,
viewportPageObject, viewportPageObject,
}) => { }) => {
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); const dataOverlayPageObject = viewportPageObject.getById('default').overlayMenu.dataOverlay;
await page.getByTestId('AddSegmentationDataOverlay-default').click(); await dataOverlayPageObject.toggle();
await page.getByText('SELECT A SEGMENTATION').click(); await dataOverlayPageObject.addSegmentation('Contours on PET');
await page.getByTestId('Contours on PET').click();
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
// Hide the overlay menu. // Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await dataOverlayPageObject.toggle();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -10,19 +10,19 @@ test.beforeEach(async ({ page }) => {
test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay menu', async ({ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay menu', async ({
page, page,
mainToolbarPageObject, mainToolbarPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
await page.getByTestId('side-panel-header-right').click(); await rightPanelPageObject.toggle();
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); const dataOverlayPageObject = viewportPageObject.getById('default').overlayMenu.dataOverlay;
await page.getByTestId('AddSegmentationDataOverlay-default').click(); await dataOverlayPageObject.toggle();
await page.getByText('SELECT A SEGMENTATION').click(); await dataOverlayPageObject.addSegmentation('ARIA RadOnc Structure Sets');
await page.getByTestId('ARIA RadOnc Structure Sets').click();
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
// Hide the overlay menu. // Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await dataOverlayPageObject.toggle();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -1,5 +1,4 @@
import { test } from 'playwright-test-coverage'; import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501'; const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501';
@ -7,17 +6,24 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('should hydrate RT reports correctly', async ({ page }) => { test('should hydrate RT reports correctly', async ({
await page.getByTestId('side-panel-header-right').click(); page,
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPreHydration); await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPreHydration);
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPostHydration); await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPostHydration);
await page.getByText('Small Sphere').click(); await rightPanelPageObject.labelMapSegmentationPanel.panel
.segmentationByText('Small Sphere')
.click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtJumpToStructure); await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtJumpToStructure);
}); });

View File

@ -1,5 +1,4 @@
import { test } from 'playwright-test-coverage'; import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.6.1.4.1.5962.99.1.2968617883.1314880426.1493322302363.3.0'; const studyInstanceUID = '1.3.6.1.4.1.5962.99.1.2968617883.1314880426.1493322302363.3.0';
@ -7,9 +6,14 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('should hydrate RT reports correctly', async ({ page }) => { test('should hydrate RT reports correctly', async ({
await page.getByTestId('side-panel-header-right').click(); page,
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
await checkForScreenshot(page, page, screenShotPaths.rtHydration2.rtPreHydration); await checkForScreenshot(page, page, screenShotPaths.rtHydration2.rtPreHydration);
// wait for 3 seconds // wait for 3 seconds
@ -34,9 +38,7 @@ test('should hydrate RT reports correctly', async ({ page }) => {
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
//
// should preserve zoom and pan and scroll position after hydration // should preserve zoom and pan and scroll position after hydration
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPostHydration); await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPostHydration);
}); });

View File

@ -6,8 +6,14 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('should hydrate an RTSTRUCT from MPR', async ({ page, mainToolbarPageObject }) => { test('should hydrate an RTSTRUCT from MPR', async ({
await page.getByTestId('side-panel-header-right').click(); page,
DOMOverlayPageObject,
leftPanelPageObject,
mainToolbarPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
@ -15,13 +21,13 @@ test('should hydrate an RTSTRUCT from MPR', async ({ page, mainToolbarPageObject
await checkForScreenshot(page, page, screenShotPaths.rtHydrationFromMPR.mprBeforeRT); await checkForScreenshot(page, page, screenShotPaths.rtHydrationFromMPR.mprBeforeRT);
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.rtHydrationFromMPR.mprAfterRT); await checkForScreenshot(page, page, screenShotPaths.rtHydrationFromMPR.mprAfterRT);
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -6,11 +6,17 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('should hydrate an RTSTRUCT and then launch MPR', async ({ page, mainToolbarPageObject }) => { test('should hydrate an RTSTRUCT and then launch MPR', async ({
await page.getByTestId('side-panel-header-right').click(); page,
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); DOMOverlayPageObject,
leftPanelPageObject,
mainToolbarPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -6,9 +6,14 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('should launch MPR with unhydrated RTSTRUCT', async ({ page, mainToolbarPageObject }) => { test('should launch MPR with unhydrated RTSTRUCT', async ({
await page.getByTestId('side-panel-header-right').click(); page,
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); leftPanelPageObject,
mainToolbarPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the rectangle tool', async ({ test('should display the rectangle tool', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -16,6 +17,6 @@ test('should display the rectangle tool', async ({
{ x: 476, y: 159 }, { x: 476, y: 159 },
{ x: 591, y: 217 }, { x: 591, y: 217 },
]); ]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot(page, page, screenShotPaths.rectangle.rectangleDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.rectangle.rectangleDisplayedCorrectly);
}); });

View File

@ -10,20 +10,21 @@ test.beforeEach(async ({ page }) => {
test('should overlay an unhydrated SEG over a display set that the SEG does NOT reference', async ({ test('should overlay an unhydrated SEG over a display set that the SEG does NOT reference', async ({
page, page,
leftPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
await page.getByTestId('study-browser-thumbnail').nth(2).dblclick(); await leftPanelPageObject.loadSeriesByDescription('Apparent Diffusion Coefficient');
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await viewportPageObject.getById('default').overlayMenu.dataOverlay.toggle();
await page.getByTestId('AddSegmentationDataOverlay-default').click(); await viewportPageObject
await page.getByText('SELECT A SEGMENTATION').click(); .getById('default')
await page.getByTestId('T2 Weighted Axial Segmentations').click(); .overlayMenu.dataOverlay.addSegmentation('T2 Weighted Axial Segmentations');
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
// Hide the overlay menu. // Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await viewportPageObject.getById('default').overlayMenu.dataOverlay.toggle();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -10,19 +10,20 @@ test.beforeEach(async ({ page }) => {
test('should launch MPR with unhydrated SEG chosen from the data overlay menu', async ({ test('should launch MPR with unhydrated SEG chosen from the data overlay menu', async ({
page, page,
mainToolbarPageObject, mainToolbarPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
await page.getByTestId('side-panel-header-right').click(); await rightPanelPageObject.toggle();
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await viewportPageObject.getById('default').overlayMenu.dataOverlay.toggle();
await page.getByTestId('AddSegmentationDataOverlay-default').click(); await viewportPageObject
await page.getByText('SELECT A SEGMENTATION').click(); .getById('default')
await page.getByTestId('Segmentation').click(); .overlayMenu.dataOverlay.addSegmentation('Segmentation');
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
// Hide the overlay menu. // Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); await viewportPageObject.getById('default').overlayMenu.dataOverlay.toggle();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -1,16 +1,15 @@
import { Page } from '@playwright/test'; import { Page } from '@playwright/test';
import { ViewportPageObject } from './pages'; import { RightPanelPageObject, ViewportPageObject } from './pages';
import { checkForScreenshot, expect, screenShotPaths, test, visitStudy } from './utils'; import { checkForScreenshot, expect, screenShotPaths, test, visitStudy } from './utils';
import { press } from './utils/keyboardUtils'; import { press } from './utils/keyboardUtils';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page, rightPanelPageObject }) => {
const studyInstanceUID = '1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046'; const studyInstanceUID = '1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046';
const mode = 'segmentation'; const mode = 'segmentation';
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
await page.getByTestId('panelSegmentationWithToolsLabelMap-btn').click(); await rightPanelPageObject.labelMapSegmentationPanel.addSegmentationButton.click();
await page.getByTestId('addSegmentation').click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
}); });
@ -18,9 +17,10 @@ test.beforeEach(async ({ page }) => {
async function performDrawingToolInteraction( async function performDrawingToolInteraction(
page: Page, page: Page,
toolName: string, toolName: string,
rightPanelPageObject: RightPanelPageObject,
viewportPageObject: ViewportPageObject viewportPageObject: ViewportPageObject
) { ) {
const brushRadiusInput = page.getByTestId(`${toolName}-radius`).locator('input'); const brushRadiusInput = rightPanelPageObject.labelMapSegmentationPanel.tools[toolName].input;
const circle = viewportPageObject.active.svg('circle').first(); const circle = viewportPageObject.active.svg('circle').first();
await expect(brushRadiusInput).toHaveValue('25'); await expect(brushRadiusInput).toHaveValue('25');
@ -57,10 +57,14 @@ async function performDrawingToolInteraction(
await page.waitForTimeout(500); await page.waitForTimeout(500);
} }
test('should resize segmentation brush tool', async ({ page, viewportPageObject }) => { test('should resize segmentation brush tool', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
await page.getByTestId('Brush-btn').click(); await page.getByTestId('Brush-btn').click();
await performDrawingToolInteraction(page, 'brush', viewportPageObject); await performDrawingToolInteraction(page, 'brush', rightPanelPageObject, viewportPageObject);
await checkForScreenshot( await checkForScreenshot(
page, page,
@ -69,7 +73,11 @@ test('should resize segmentation brush tool', async ({ page, viewportPageObject
); );
}); });
test('should resize segmentation eraser tool', async ({ page, viewportPageObject }) => { test('should resize segmentation eraser tool', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
await page.getByTestId('Brush-btn').click(); await page.getByTestId('Brush-btn').click();
await page.getByTestId('brush-radius').locator('input').fill('99.5'); await page.getByTestId('brush-radius').locator('input').fill('99.5');
@ -82,7 +90,7 @@ test('should resize segmentation eraser tool', async ({ page, viewportPageObject
await page.waitForTimeout(500); await page.waitForTimeout(500);
await performDrawingToolInteraction(page, 'eraser', viewportPageObject); await performDrawingToolInteraction(page, 'eraser', rightPanelPageObject, viewportPageObject);
await checkForScreenshot( await checkForScreenshot(
page, page,
@ -91,8 +99,12 @@ test('should resize segmentation eraser tool', async ({ page, viewportPageObject
); );
}); });
test('should resize segmentation threshold tool', async ({ page, viewportPageObject }) => { test('should resize segmentation threshold tool', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
await page.getByTestId('Threshold-btn').click(); await page.getByTestId('Threshold-btn').click();
await performDrawingToolInteraction(page, 'threshold', viewportPageObject); await performDrawingToolInteraction(page, 'threshold', rightPanelPageObject, viewportPageObject);
}); });

View File

@ -1,5 +1,4 @@
import { test } from 'playwright-test-coverage'; import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.256467663913010332776401703474716742458'; const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.256467663913010332776401703474716742458';
@ -7,9 +6,14 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('should hydrate SEG reports correctly', async ({ page }) => { test('should hydrate SEG reports correctly', async ({
await page.getByTestId('side-panel-header-right').click(); page,
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByDescription('SEG');
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.segHydration.segPreHydration); await checkForScreenshot(page, page, screenShotPaths.segHydration.segPreHydration);
@ -33,7 +37,7 @@ test('should hydrate SEG reports correctly', async ({ page }) => {
} }
}); });
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.segHydration.segPostHydration); await checkForScreenshot(page, page, screenShotPaths.segHydration.segPostHydration);

View File

@ -16,6 +16,8 @@ test.beforeEach(async ({ page }) => {
test.describe('3D four up SEG hydration', async () => { test.describe('3D four up SEG hydration', async () => {
test('should properly hydrate SEG from 3D four up layout', async ({ test('should properly hydrate SEG from 3D four up layout', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
mainToolbarPageObject, mainToolbarPageObject,
}) => { }) => {
await mainToolbarPageObject.layoutSelection.threeDFourUp.click(); await mainToolbarPageObject.layoutSelection.threeDFourUp.click();
@ -30,7 +32,7 @@ test.describe('3D four up SEG hydration', async () => {
screenShotPaths.segHydrationFrom3DFourUp.threeDFourUpBeforeSEG screenShotPaths.segHydrationFrom3DFourUp.threeDFourUpBeforeSEG
); );
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByDescription('SEG');
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot( await checkForScreenshot(
@ -39,7 +41,7 @@ test.describe('3D four up SEG hydration', async () => {
screenShotPaths.segHydrationFrom3DFourUp.threeDFourUpAfterSEG screenShotPaths.segHydrationFrom3DFourUp.threeDFourUpAfterSEG
); );
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot( await checkForScreenshot(

View File

@ -6,20 +6,26 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('should properly display MPR for MR', async ({ page, mainToolbarPageObject }) => { test('should properly display MPR for MR', async ({
await page.getByTestId('side-panel-header-right').click(); page,
DOMOverlayPageObject,
leftPanelPageObject,
mainToolbarPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprBeforeSEG); await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprBeforeSEG);
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByDescription('SEG');
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSEG); await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSEG);
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSegHydrated); await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSegHydrated);

View File

@ -6,11 +6,17 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('should properly display MPR for MR', async ({ page, mainToolbarPageObject }) => { test('should properly display MPR for MR', async ({
await page.getByTestId('side-panel-header-right').click(); page,
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); DOMOverlayPageObject,
leftPanelPageObject,
mainToolbarPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByDescription('SEG');
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.segHydrationThenMPR.segPostHydration); await checkForScreenshot(page, page, screenShotPaths.segHydrationThenMPR.segPostHydration);

View File

@ -6,9 +6,14 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('should launch MPR with unhydrated SEG', async ({ page, mainToolbarPageObject }) => { test('should launch MPR with unhydrated SEG', async ({
await page.getByTestId('side-panel-header-right').click(); page,
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); leftPanelPageObject,
mainToolbarPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByDescription('SEG');
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View File

@ -1,5 +1,4 @@
import { test } from 'playwright-test-coverage'; import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.7695.4007.324475281161490036195179843543'; const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.7695.4007.324475281161490036195179843543';
@ -7,10 +6,15 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('should hydrate SR reports correctly', async ({ page }) => { test('should hydrate SR reports correctly', async ({
await page.getByTestId('side-panel-header-right').click(); page,
await page.getByTestId('trackedMeasurements-btn').click(); DOMOverlayPageObject,
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); leftPanelPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await rightPanelPageObject.measurementsPanel.select();
await leftPanelPageObject.loadSeriesByModality('SR');
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
await checkForScreenshot(page, page, screenShotPaths.srHydration.srPreHydration); await checkForScreenshot(page, page, screenShotPaths.srHydration.srPreHydration);
@ -33,7 +37,7 @@ test('should hydrate SR reports correctly', async ({ page }) => {
} }
}); });
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
await checkForScreenshot(page, page, screenShotPaths.srHydration.srPostHydration); await checkForScreenshot(page, page, screenShotPaths.srHydration.srPostHydration);
@ -56,7 +60,7 @@ test('should hydrate SR reports correctly', async ({ page }) => {
} }
}); });
await page.getByTestId('data-row').first().click(); await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
await checkForScreenshot(page, page, screenShotPaths.srHydration.srJumpToMeasurement); await checkForScreenshot(page, page, screenShotPaths.srHydration.srJumpToMeasurement);
}); });

View File

@ -21,17 +21,20 @@ test.beforeEach(async ({ page }) => {
test('should hydrate SCOORD3D probe measurements correctly', async ({ test('should hydrate SCOORD3D probe measurements correctly', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
// Wait for the side panel to be visible and clickable // Wait for the side panel to be visible and clickable
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
// Navigate to the tracked measurements panel // Navigate to the tracked measurements panel
await page.getByTestId('side-panel-header-right').click({ timeout: 15000 }); await rightPanelPageObject.toggle();
await page.getByTestId('trackedMeasurements-btn').click(); await rightPanelPageObject.measurementsPanel.select();
// Double-click on the study browser thumbnail to load the SR // Double-click on the study browser thumbnail to load the SR
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByModality('SR');
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
// Wait for the SR to load and stabilize before taking screenshot // Wait for the SR to load and stabilize before taking screenshot
@ -68,10 +71,13 @@ test('should hydrate SCOORD3D probe measurements correctly', async ({
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
// Wait for the hydrate button to be visible and clickable // Wait for the hydrate button to be visible and clickable
await page.getByTestId('yes-hydrate-btn').waitFor({ state: 'visible', timeout: 15000 }); await DOMOverlayPageObject.viewport.segmentationHydration.yes.button.waitFor({
state: 'visible',
timeout: 15000,
});
// Click the hydrate button to load the SCOORD3D probe measurements // Click the hydrate button to load the SCOORD3D probe measurements
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for hydration to complete and rendering to stabilize // Wait for hydration to complete and rendering to stabilize
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
@ -86,14 +92,17 @@ test('should hydrate SCOORD3D probe measurements correctly', async ({
// Verify the measurements list has the correct probe measurements // Verify the measurements list has the correct probe measurements
const measurementRows = page.getByTestId('data-row'); const measurementRows = page.getByTestId('data-row');
const rowCount = await measurementRows.count(); const rowCount = await measurementRows.count();
expect(rowCount).toBeGreaterThan(0); expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBeGreaterThan(
0
);
// Verify that the measurements are probe measurements (not other types) // Verify that the measurements are probe measurements (not other types)
for (let i = 0; i < rowCount; i++) { for (let i = 0; i < rowCount; i++) {
const row = measurementRows.nth(i); const measurementText = await rightPanelPageObject.measurementsPanel.panel
const rowText = await row.textContent(); .nthMeasurement(i)
.locator.textContent();
// Probe measurements should be present, verify they're not other measurement types // Probe measurements should be present, verify they're not other measurement types
expect(rowText).toBeTruthy(); expect(measurementText).toBeTruthy();
} }
// Test jumping to a specific measurement by scrolling and clicking // Test jumping to a specific measurement by scrolling and clicking
@ -117,7 +126,7 @@ test('should hydrate SCOORD3D probe measurements correctly', async ({
}); });
// Click on a data row to jump to the measurement // Click on a data row to jump to the measurement
await page.getByTestId('data-row').first().click(); await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
// Take screenshot showing the jump to measurement functionality - use viewport locator // Take screenshot showing the jump to measurement functionality - use viewport locator
await checkForScreenshot( await checkForScreenshot(
@ -129,20 +138,26 @@ test('should hydrate SCOORD3D probe measurements correctly', async ({
test('should display SCOORD3D probe measurements correctly', async ({ test('should display SCOORD3D probe measurements correctly', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
// Wait for the side panel to be visible and clickable // Wait for the side panel to be visible and clickable
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
// First hydrate the SR to load the measurements // First hydrate the SR to load the measurements
await page.getByTestId('side-panel-header-right').click({ timeout: 15000 }); await rightPanelPageObject.toggle();
await page.getByTestId('trackedMeasurements-btn').click(); await rightPanelPageObject.measurementsPanel.select();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByModality('SR');
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
// Wait for the hydrate button to be visible and clickable // Wait for the hydrate button to be visible and clickable
await page.getByTestId('yes-hydrate-btn').waitFor({ state: 'visible', timeout: 15000 }); await DOMOverlayPageObject.viewport.segmentationHydration.yes.button.waitFor({
await page.getByTestId('yes-hydrate-btn').click(); state: 'visible',
timeout: 15000,
});
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
// Zoom to show the probe measurements clearly // Zoom to show the probe measurements clearly
@ -175,15 +190,15 @@ test('should display SCOORD3D probe measurements correctly', async ({
); );
// Verify the measurements list has the correct probe measurements and not others // Verify the measurements list has the correct probe measurements and not others
const measurementRows = page.getByTestId('data-row'); const rowCount = await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount();
const rowCount = await measurementRows.count();
expect(rowCount).toBeGreaterThan(0); expect(rowCount).toBeGreaterThan(0);
// Verify that the measurements are probe measurements (not other types like rectangle) // Verify that the measurements are probe measurements (not other types like rectangle)
for (let i = 0; i < rowCount; i++) { for (let i = 0; i < rowCount; i++) {
const row = measurementRows.nth(i); const measurementText = await rightPanelPageObject.measurementsPanel.panel
const rowText = await row.textContent(); .nthMeasurement(i)
.locator.textContent();
// Probe measurements should be present, verify they're not other measurement types // Probe measurements should be present, verify they're not other measurement types
expect(rowText).toBeTruthy(); expect(measurementText).toBeTruthy();
} }
}); });

View File

@ -22,17 +22,20 @@ test.beforeEach(async ({ page }) => {
// //
test('should hydrate SCOORD rectangle measurements correctly', async ({ test('should hydrate SCOORD rectangle measurements correctly', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
// Wait for the side panel to be visible and clickable // Wait for the side panel to be visible and clickable
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
// Navigate to the tracked measurements panel // Navigate to the tracked measurements panel
await page.getByTestId('side-panel-header-right').click({ timeout: 15000 }); await rightPanelPageObject.toggle();
await page.getByTestId('trackedMeasurements-btn').click(); await rightPanelPageObject.measurementsPanel.select();
// Double-click on the study browser thumbnail to load the SR // Double-click on the study browser thumbnail to load the SR
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByModality('SR');
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
// Wait for the SR to load and stabilize before taking screenshot // Wait for the SR to load and stabilize before taking screenshot
@ -69,10 +72,13 @@ test('should hydrate SCOORD rectangle measurements correctly', async ({
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
// Wait for the hydrate button to be visible and clickable // Wait for the hydrate button to be visible and clickable
await page.getByTestId('yes-hydrate-btn').waitFor({ state: 'visible', timeout: 15000 }); await DOMOverlayPageObject.viewport.segmentationHydration.yes.button.waitFor({
state: 'visible',
timeout: 15000,
});
// Click the hydrate button to load the SCOORD rectangle measurements // Click the hydrate button to load the SCOORD rectangle measurements
await page.getByTestId('yes-hydrate-btn').click(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for hydration to complete and rendering to stabilize // Wait for hydration to complete and rendering to stabilize
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
@ -85,16 +91,16 @@ test('should hydrate SCOORD rectangle measurements correctly', async ({
); );
// Verify the measurements list has the correct rectangle measurements // Verify the measurements list has the correct rectangle measurements
const measurementRows = page.getByTestId('data-row'); const rowCount = await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount();
const rowCount = await measurementRows.count();
expect(rowCount).toBeGreaterThan(0); expect(rowCount).toBeGreaterThan(0);
// Verify that the measurements are rectangle measurements (not other types) // Verify that the measurements are rectangle measurements (not other types)
for (let i = 0; i < rowCount; i++) { for (let i = 0; i < rowCount; i++) {
const row = measurementRows.nth(i); const measurementText = await rightPanelPageObject.measurementsPanel.panel
const rowText = await row.textContent(); .nthMeasurement(i)
.locator.textContent();
// Rectangle measurements should be present, verify they're not other measurement types // Rectangle measurements should be present, verify they're not other measurement types
expect(rowText).toBeTruthy(); expect(measurementText).toBeTruthy();
} }
// Test jumping to a specific measurement by scrolling and clicking // Test jumping to a specific measurement by scrolling and clicking
@ -118,7 +124,7 @@ test('should hydrate SCOORD rectangle measurements correctly', async ({
}); });
// Click on a data row to jump to the measurement // Click on a data row to jump to the measurement
await page.getByTestId('data-row').first().click(); await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
// Take screenshot showing the jump to measurement functionality - use viewport locator // Take screenshot showing the jump to measurement functionality - use viewport locator
await checkForScreenshot( await checkForScreenshot(
@ -130,20 +136,26 @@ test('should hydrate SCOORD rectangle measurements correctly', async ({
test('should display SCOORD rectangle measurements correctly', async ({ test('should display SCOORD rectangle measurements correctly', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
// Wait for the side panel to be visible and clickable // Wait for the side panel to be visible and clickable
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
// First hydrate the SR to load the measurements // First hydrate the SR to load the measurements
await page.getByTestId('side-panel-header-right').click({ timeout: 15000 }); await rightPanelPageObject.toggle();
await page.getByTestId('trackedMeasurements-btn').click(); await rightPanelPageObject.measurementsPanel.select();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await leftPanelPageObject.loadSeriesByModality('SR');
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
// Wait for the hydrate button to be visible and clickable // Wait for the hydrate button to be visible and clickable
await page.getByTestId('yes-hydrate-btn').waitFor({ state: 'visible', timeout: 15000 }); await DOMOverlayPageObject.viewport.segmentationHydration.yes.button.waitFor({
await page.getByTestId('yes-hydrate-btn').click(); state: 'visible',
timeout: 15000,
});
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
// Zoom to show the rectangle measurements clearly // Zoom to show the rectangle measurements clearly
@ -176,15 +188,15 @@ test('should display SCOORD rectangle measurements correctly', async ({
); );
// Verify the measurements list has the correct rectangle measurements and not others // Verify the measurements list has the correct rectangle measurements and not others
const measurementRows = page.getByTestId('data-row'); const rowCount = await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount();
const rowCount = await measurementRows.count();
expect(rowCount).toBeGreaterThan(0); expect(rowCount).toBeGreaterThan(0);
// Verify that the measurements are rectangle measurements (not other types like probe) // Verify that the measurements are rectangle measurements (not other types like probe)
for (let i = 0; i < rowCount; i++) { for (let i = 0; i < rowCount; i++) {
const row = measurementRows.nth(i); const measurementText = await rightPanelPageObject.measurementsPanel.panel
const rowText = await row.textContent(); .nthMeasurement(i)
.locator.textContent();
// Rectangle measurements should be present, verify they're not other measurement types // Rectangle measurements should be present, verify they're not other measurement types
expect(rowText).toBeTruthy(); expect(measurementText).toBeTruthy();
} }
}); });

View File

@ -7,55 +7,39 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
}); });
test('checks basic add, rename, delete segments from panel', async ({ page }) => { test('checks basic add, rename, delete segments from panel', async ({ rightPanelPageObject }) => {
// Segmentation Panel should already be open // Segmentation Panel should already be open
const segmentationPanel = page.getByTestId('panelSegmentationWithToolsLabelMap-btn'); const segmentationPanel = rightPanelPageObject.labelMapSegmentationPanel.menuButton;
await expect(segmentationPanel).toBeVisible(); await expect(segmentationPanel).toBeVisible();
// Switch to labelmap tab. // Switch to labelmap tab.
segmentationPanel.click(); segmentationPanel.click();
// Add segmentation // Add segmentation
const addSegmentationBtn = page.getByTestId('addSegmentation'); await rightPanelPageObject.labelMapSegmentationPanel.addSegmentationButton.click();
await addSegmentationBtn.click();
// Expect new segmentation and blank segment named "Segment 1" // Expect new segmentation and blank segment named "Segment 1"
await expect(page.getByTestId('data-row')).toHaveCount(1); const segment1 = rightPanelPageObject.labelMapSegmentationPanel.panel.nthSegmentation(0);
await expect(page.getByTestId('data-row')).toContainText('Segment 1'); expect(await rightPanelPageObject.labelMapSegmentationPanel.panel.getSegmentationCount()).toBe(1);
await expect(segment1.locator).toContainText('Segment 1');
// Rename // Rename
const segment1Dropdown = page await segment1.actions.rename('Segment One');
.getByTestId('data-row')
.first()
.getByRole('button', { name: 'Actions' });
await segment1Dropdown.click();
const renameButton = page.getByRole('menuitem', { name: 'Rename' }); await expect(segment1.locator).toContainText('Segment One');
await expect(renameButton).toBeVisible(); await expect(segment1.locator).not.toContainText('Segment 1');
await renameButton.click();
const renameDialog = page.getByRole('dialog', { name: 'Edit Segment Label' });
await expect(renameDialog).toBeVisible();
const renameInput = page.getByRole('textbox', { name: 'Enter new label' });
await renameInput.fill('Segment One');
await page.getByTestId('input-dialog-save-button').click();
await expect(page.getByTestId('data-row')).toContainText('Segment One');
await expect(page.getByTestId('data-row')).not.toContainText('Segment 1');
// Delete // Delete
await segment1Dropdown.click(); await segment1.actions.delete();
const deleteButton = page.getByRole('menuitem', { name: 'Delete' });
await expect(deleteButton).toBeVisible();
await deleteButton.click();
await expect(page.getByTestId('data-row')).toHaveCount(0); expect(await rightPanelPageObject.labelMapSegmentationPanel.panel.getSegmentationCount()).toBe(0);
}); });
test('checks saved segmentations loads and jumps to slices', async ({ test('checks saved segmentations loads and jumps to slices', async ({
page, page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
const viewportInfoBottomRight = viewportPageObject.active.overlayText.bottomRight; const viewportInfoBottomRight = viewportPageObject.active.overlayText.bottomRight;
@ -63,39 +47,34 @@ test('checks saved segmentations loads and jumps to slices', async ({
await expect(viewportInfoBottomRight).toContainText('1/', { timeout: 10000 }); await expect(viewportInfoBottomRight).toContainText('1/', { timeout: 10000 });
// Add Segmentations // Add Segmentations
const segmentationsThumbnail = page await leftPanelPageObject.loadSeriesByModality('SEG');
.getByTestId('study-browser-thumbnail-no-image')
.getByRole('button', { name: 'Segmentation' });
await segmentationsThumbnail.dblclick();
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
// Confirm open segmentation // Confirm open segmentation
const viewportNotification = page.getByTestId('viewport-notification'); await expect(DOMOverlayPageObject.viewport.segmentationHydration.locator).toBeVisible();
await expect(viewportNotification).toBeVisible(); await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.getByTestId('yes-hydrate-btn').click();
// Segmentation Panel should already be open // Segmentation Panel should already be open
const segmentationPanel = page.getByTestId('panelSegmentationWithToolsLabelMap-btn'); const segmentationPanel = rightPanelPageObject.labelMapSegmentationPanel.menuButton;
await expect(segmentationPanel).toBeVisible(); await expect(segmentationPanel).toBeVisible();
// Confirm spleen jumps to slice 17 // Confirm spleen jumps to slice 17
// First iteration repeat to account for segmentation loading delays // First iteration repeat to account for segmentation loading delays
const spleenRow = page.getByTestId('data-row').filter({ hasText: 'Spleen' });
await expect(async () => { await expect(async () => {
await spleenRow.click(); await rightPanelPageObject.labelMapSegmentationPanel.panel.segmentationByText('Spleen').click();
await expect(viewportInfoBottomRight).toContainText('17/'); await expect(viewportInfoBottomRight).toContainText('17/');
}).toPass({ }).toPass({
timeout: 10000, timeout: 10000,
}); });
// Esophagus - 5 // Esophagus - 5
const esophagusRow = page.getByTestId('data-row').filter({ hasText: 'Esophagus' }); await rightPanelPageObject.labelMapSegmentationPanel.panel
await esophagusRow.click(); .segmentationByText('Esophagus')
.click();
await expect(viewportInfoBottomRight).toContainText('5/'); await expect(viewportInfoBottomRight).toContainText('5/');
// Pancreas - 22 // Pancreas - 22
const pancreasRow = page.getByTestId('data-row').filter({ hasText: 'Pancreas' }); await rightPanelPageObject.labelMapSegmentationPanel.panel.segmentationByText('Pancreas').click();
await pancreasRow.click();
await expect(viewportInfoBottomRight).toContainText('22/'); await expect(viewportInfoBottomRight).toContainText('22/');
}); });

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the spline tool', async ({ test('should display the spline tool', async ({
page, page,
DOMOverlayPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -20,6 +21,6 @@ test('should display the spline tool', async ({
{ x: 482, y: 493 }, { x: 482, y: 493 },
{ x: 383, y: 461 }, { x: 383, y: 461 },
]); ]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await checkForScreenshot(page, page, screenShotPaths.spline.splineDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.spline.splineDisplayedCorrectly);
}); });

View File

@ -3,21 +3,22 @@ import { downloadAsString } from './utils/download';
test('should create and download the TMTV CSV report correctly', async ({ test('should create and download the TMTV CSV report correctly', async ({
page, page,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501'; const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501';
const mode = 'tmtv'; const mode = 'tmtv';
await visitStudy(page, studyInstanceUID, mode, 10000); await visitStudy(page, studyInstanceUID, mode, 10000);
await page.getByTestId('addSegmentation').click(); await rightPanelPageObject.tmtvPanel.addSegmentationButton.click();
await page.getByTestId('Brush-btn').click(); await rightPanelPageObject.tmtvPanel.tools.brush.click();
await viewportPageObject.getById('ctAXIAL').normalizedClickAt([{ x: 0.5, y: 0.5 }]); await viewportPageObject.getById('ctAXIAL').normalizedClickAt([{ x: 0.5, y: 0.5 }]);
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
const downloadPromise = page.waitForEvent('download'); const downloadPromise = page.waitForEvent('download');
await page.getByTestId('exportTmtvCsvReport').click(); await rightPanelPageObject.tmtvPanel.exportTmtvCsvReport();
const download = await downloadPromise; const download = await downloadPromise;
expect(download.suggestedFilename(), 'Not the correct file name for the TMTV CSV report.').toBe( expect(download.suggestedFilename(), 'Not the correct file name for the TMTV CSV report.').toBe(

View File

@ -2,6 +2,7 @@ import { expect, clearAllAnnotations, getTMTVModalityUnit, test, visitStudy } fr
test.skip('pets where SUV cannot be calculated should show same unit in TMTV as in Basic Viewer.', async ({ test.skip('pets where SUV cannot be calculated should show same unit in TMTV as in Basic Viewer.', async ({
page, page,
leftPanelPageObject,
mainToolbarPageObject, mainToolbarPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -10,7 +11,7 @@ test.skip('pets where SUV cannot be calculated should show same unit in TMTV as
await visitStudy(page, studyInstanceUID, mode, 10000); await visitStudy(page, studyInstanceUID, mode, 10000);
// Show sidebar // Show sidebar
await page.getByTestId('side-panel-header-left').click(); await leftPanelPageObject.toggle();
// Change to image where SUV cannot be calculated // Change to image where SUV cannot be calculated
await viewportPageObject.getNth(3).normalizedClickAt([{ x: 0.5, y: 0.5 }]); await viewportPageObject.getNth(3).normalizedClickAt([{ x: 0.5, y: 0.5 }]);

View File

@ -2,6 +2,7 @@ import { expect, test, visitStudy } from './utils';
test('should restrict the percentage of max SUV to be between 0 and 1', async ({ test('should restrict the percentage of max SUV to be between 0 and 1', async ({
page, page,
rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501'; const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501';
@ -10,49 +11,51 @@ test('should restrict the percentage of max SUV to be between 0 and 1', async ({
await viewportPageObject.getById('ptAXIAL').normalizedClickAt([{ x: 0.5, y: 0.5 }]); await viewportPageObject.getById('ptAXIAL').normalizedClickAt([{ x: 0.5, y: 0.5 }]);
await page.getByTestId('addSegmentation').click(); await rightPanelPageObject.tmtvPanel.addSegmentationButton.click();
await page.getByTestId('RectangleROIStartEndThreshold-btn').click();
await page.getByTestId('percentage-of-max-suv-input').fill('0'); const rectangleROIThreshold = rightPanelPageObject.tmtvPanel.tools.rectangleROIThreshold;
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0'); await rectangleROIThreshold.click();
await page.getByTestId('percentage-of-max-suv-input').fill('0.27'); await rectangleROIThreshold.setPercentageOfMaxSUV('0');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.27'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0');
await page.getByTestId('percentage-of-max-suv-input').fill('0.9467'); await rectangleROIThreshold.setPercentageOfMaxSUV('0.27');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.9467'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.27');
await page.getByTestId('percentage-of-max-suv-input').fill('.'); await rectangleROIThreshold.setPercentageOfMaxSUV('0.9467');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.9467');
await page.getByTestId('percentage-of-max-suv-input').fill('1'); await rectangleROIThreshold.setPercentageOfMaxSUV('.');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('1'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.');
await page.getByTestId('percentage-of-max-suv-input').fill('1.1'); await rectangleROIThreshold.setPercentageOfMaxSUV('1');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('1'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('1');
await page.getByTestId('percentage-of-max-suv-input').fill('1806'); await rectangleROIThreshold.setPercentageOfMaxSUV('1.1');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('1'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('1');
await page.getByTestId('percentage-of-max-suv-input').fill(''); await rectangleROIThreshold.setPercentageOfMaxSUV('1806');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe(''); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('1');
await rectangleROIThreshold.setPercentageOfMaxSUV('');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('');
// Add some valid input for the tests that follow. Note that when invalid input is // Add some valid input for the tests that follow. Note that when invalid input is
// entered the previous valid input is retained. // entered the previous valid input is retained.
await page.getByTestId('percentage-of-max-suv-input').fill('0.275'); await rectangleROIThreshold.setPercentageOfMaxSUV('0.275');
await page.getByTestId('percentage-of-max-suv-input').fill('9'); await rectangleROIThreshold.setPercentageOfMaxSUV('9');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.275'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.275');
await page.getByTestId('percentage-of-max-suv-input').fill('-678'); await rectangleROIThreshold.setPercentageOfMaxSUV('-678');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.275'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.275');
await page.getByTestId('percentage-of-max-suv-input').fill('+'); await rectangleROIThreshold.setPercentageOfMaxSUV('+');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.275'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.275');
await page.getByTestId('percentage-of-max-suv-input').fill('-'); await rectangleROIThreshold.setPercentageOfMaxSUV('-');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.275'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.275');
await page.getByTestId('percentage-of-max-suv-input').fill('e'); await rectangleROIThreshold.setPercentageOfMaxSUV('e');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.275'); expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.275');
}); });

View File

@ -25,13 +25,13 @@ test('should render scroll bars with the correct look-and-feel', async ({ page }
// Wait for the expanded row to be visible and rendered // Wait for the expanded row to be visible and rendered
await expandedStudyRow.waitFor({ state: 'visible', timeout: 10000 }); await expandedStudyRow.waitFor({ state: 'visible', timeout: 10000 });
await expandedStudyRow.scrollIntoViewIfNeeded(); await expandedStudyRow.scrollIntoViewIfNeeded();
// Wait for content to load and stabilize, including any lazy-loaded items // Wait for content to load and stabilize, including any lazy-loaded items
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
// Wait for network to be idle to ensure all content is loaded // Wait for network to be idle to ensure all content is loaded
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
// Additional wait to ensure scrollbar rendering is stable // Additional wait to ensure scrollbar rendering is stable
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

View File

@ -0,0 +1,134 @@
import { Locator, Page } from '@playwright/test';
export class DOMOverlayPageObject {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
get dialog() {
const page = this.page;
return {
get confirmation() {
const cancelButton = page.getByTestId('untracked-series-modal-cancel-button');
const confirmButton = page.getByTestId('untracked-series-modal-confirm-button');
return {
cancel: {
button: cancelButton,
click: async () => {
await cancelButton.click();
},
},
confirm: {
button: confirmButton,
click: async () => {
await confirmButton.click();
},
},
};
},
get input() {
const locator = page.getByTestId('dialog-input');
const saveButton = page.getByTestId('input-dialog-save-button');
return {
locator,
fill: async (text: string) => {
await locator.fill(text);
},
fillAndSave: async (text: string) => {
await locator.fill(text);
await saveButton.click();
},
save: async () => {
await saveButton.click();
},
};
},
title: page.locator('[role="dialog"] h2'),
};
}
get viewport() {
const page = this.page;
return {
get annotationContextMenu() {
return {
get addLabel() {
const button = page.getByTestId('context-menu-item').filter({ hasText: 'Add Label' });
return {
locator: button,
click: async () => {
await button.click();
},
};
},
get delete() {
const button = page.getByTestId('context-menu-item').filter({ hasText: 'Delete' });
return {
locator: button,
click: async () => {
await button.click();
},
};
},
open: async (annotation: Locator) => {
await annotation.click({ button: 'right', force: true });
},
};
},
get measurementTracking() {
const cancelButton = page.getByTestId('prompt-begin-tracking-cancel-btn');
const noAndNotAskAgainButton = page.getByTestId(
'prompt-begin-tracking-no-do-not-ask-again-btn'
);
const confirmButton = page.getByTestId('prompt-begin-tracking-yes-btn');
return {
locator: page.getByTestId('viewport-notification'),
cancel: {
button: cancelButton,
click: async () => {
await cancelButton.click();
},
},
noAndNotAskAgain: {
button: noAndNotAskAgainButton,
click: async () => {
await noAndNotAskAgainButton.click();
},
},
confirm: {
button: confirmButton,
click: async () => {
await confirmButton.click();
},
},
};
},
get segmentationHydration() {
const noButton = page.getByTestId('no-hydrate-btn');
const yesButton = page.getByTestId('yes-hydrate-btn');
return {
locator: page.getByTestId('viewport-notification'),
no: {
button: noButton,
click: async () => {
await noButton.click();
},
},
yes: {
button: yesButton,
click: async () => {
await yesButton.click();
},
},
};
},
async getModalityLoadBadgeCount() {
return await page.locator('css=div[data-cy^="ModalityLoadBadge-"]').count();
},
};
}
}

View File

@ -0,0 +1,29 @@
import { Page } from '@playwright/test';
export class DataOverlayPageObject {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
async toggle(viewportId: string = 'default') {
await this.page.getByTestId(`dataOverlayMenu-${viewportId}-btn`).click();
}
async addSegmentation(segmentation: string, viewportId: string = 'default') {
await this.page.getByTestId(`AddSegmentationDataOverlay-${viewportId}`).click();
await this.page.getByText('SELECT A SEGMENTATION').click();
await this.page.getByTestId(segmentation).click();
}
async changeSegmentation(from: string, to: string) {
await this.page.getByTestId(`overlay-ds-select-value-${from.toUpperCase()}`).click();
await this.page.getByTestId(`${to}-SEG`).click();
}
async remove(segmentation: string) {
await this.page.getByTestId(`overlay-ds-more-button-${segmentation}`).click();
await this.page.getByTestId(`overlay-ds-remove-button-${segmentation}`).click();
}
}

View File

@ -0,0 +1,35 @@
import { Page } from '@playwright/test';
export class LeftPanelPageObject {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
async toggle() {
await this.page.getByTestId('side-panel-header-left').click();
}
get thumbnails() {
return this.page.locator('[data-cy^="study-browser-thumbnail"]');
}
async loadSeriesByModality(modality: string, nth: number = 0) {
const matchingThumbnail = this.thumbnails
.filter({
has: this.page.locator('[data-cy="series-modality-label"]', { hasText: modality }),
})
.nth(nth);
await matchingThumbnail.dblclick();
}
async loadSeriesByDescription(description: string, nth: number = 0) {
const matchingThumbnail = this.thumbnails
.filter({
has: this.page.locator('[data-cy="series-description-label"]', { hasText: description }),
})
.nth(nth);
await matchingThumbnail.dblclick();
}
}

View File

@ -7,6 +7,15 @@ export class MainToolbarPageObject {
this.page = page; this.page = page;
} }
get crosshairs() {
const button = this.page.getByTestId('Crosshairs');
return {
button,
async click() {
await button.click();
},
};
}
get layoutSelection() { get layoutSelection() {
const page = this.page; const page = this.page;
@ -96,6 +105,15 @@ export class MainToolbarPageObject {
return { return {
...measurementTools, ...measurementTools,
get selectedTool() {
const button = page.getByTestId('MeasurementTools-split-button-primary');
return {
button,
async click() {
await button.click();
},
};
},
get arrowAnnotate() { get arrowAnnotate() {
const button = page.getByTestId('ArrowAnnotate'); const button = page.getByTestId('ArrowAnnotate');
return { return {

View File

@ -0,0 +1,271 @@
import { Locator, Page } from '@playwright/test';
import { DOMOverlayPageObject } from './DOMOverlayPageObject';
export class RightPanelPageObject {
readonly page: Page;
private readonly DOMOverlayPageObject: DOMOverlayPageObject;
constructor(page: Page) {
this.page = page;
this.DOMOverlayPageObject = new DOMOverlayPageObject(page);
}
private getActionsMenu(row: Locator) {
const actionsButton = row.getByTestId('actionsMenuTrigger');
return {
button: actionsButton,
click: async () => {
await actionsButton.click();
},
delete: async () => {
await actionsButton.click();
await this.page.getByTestId('Delete').click();
},
toggleLock: async () => {
await actionsButton.click();
await this.page.getByTestId('LockToggle').click();
},
unlock: async () => {
await actionsButton.click();
await this.page.getByTestId('Unlock').click();
},
rename: async (text: string) => {
await actionsButton.click();
await this.page.getByTestId('Rename').click();
await this.DOMOverlayPageObject.dialog.input.fillAndSave(text);
},
};
}
private getPanelRowDataObject(row: Locator) {
const getActionsMenu = (row: Locator) => this.getActionsMenu.bind(this)(row);
return {
get actions() {
return getActionsMenu(row);
},
click: async () => {
await row.click();
},
locator: row,
toggleVisibility: async () => {
await row.getByTestId('data-row-visibility-toggle').click();
},
};
}
private getPanelRowByIdx(index: number) {
const row = this.page.getByTestId('data-row').nth(index);
return this.getPanelRowDataObject(row);
}
private getPanelRowByText(text: string) {
const row = this.page.getByTestId('data-row').filter({ hasText: text });
return this.getPanelRowDataObject(row);
}
async toggle() {
await this.page.getByTestId('side-panel-header-right').click();
}
get measurementsPanel() {
const page = this.page;
const getMeasurementByIdx = (index: number) => this.getPanelRowByIdx(index);
const getMeasurementByText = (text: string) => this.getPanelRowByText(text);
const menuButton = page.getByTestId('trackedMeasurements-btn');
return {
menuButton,
panel: {
deleteAll: async () => {
await page.getByRole('button', { name: 'Delete' }).click();
},
getMeasurementCount: async () => {
return await page.getByTestId('data-row').count();
},
locator: page.getByTestId('trackedMeasurements-panel').last(),
nthMeasurement(index: number) {
return getMeasurementByIdx(index);
},
measurementByText(text: string) {
return getMeasurementByText(text);
},
},
select: async () => {
await menuButton.click();
},
};
}
private get addSegmentationButton() {
const button = this.page.getByTestId('addSegmentation');
return {
button,
click: async () => {
await button.click();
},
};
}
private get segmentationPanel() {
const page = this.page;
const getSegmentationByIdx = (index: number) => this.getPanelRowByIdx(index);
const getSegmentationByText = (text: string) => this.getPanelRowByText(text);
return {
getSegmentationCount: async () => {
return await page.getByTestId('data-row').count();
},
// No data-cy exists in this panel, using Segmentation header button
locator: page.getByRole('button', { name: 'Segmentations' }),
nthSegmentation(index: number) {
return getSegmentationByIdx(index);
},
segmentationByText(text: string) {
return getSegmentationByText(text);
},
};
}
get contourSegmentationPanel() {
const page = this.page;
const addSegmentationButton = this.addSegmentationButton;
const panel = this.segmentationPanel;
const menuButton = page.getByTestId('panelSegmentationWithToolsContour-btn');
return {
addSegmentationButton,
menuButton,
panel,
select: async () => {
await menuButton.click();
},
};
}
get labelMapSegmentationPanel() {
const page = this.page;
const addSegmentationButton = this.addSegmentationButton;
const panel = this.segmentationPanel;
const menuButton = page.getByTestId('panelSegmentationWithToolsLabelMap-btn');
return {
addSegmentationButton,
menuButton,
panel,
select: async () => {
await menuButton.click();
},
tools: {
get brush() {
const button = page.getByTestId('Brush-btn');
const input = page.locator(`css=div[data-cy="brush-radius"] input`);
return {
button,
input,
click: async () => {
await button.click();
},
setRadius: async (radius: number) => {
await input.fill(radius.toString());
},
};
},
get eraser() {
const button = page.getByTestId('Eraser-btn');
const input = page.locator(`css=div[data-cy="eraser-radius"] input`);
return {
button,
input,
click: async () => {
await button.click();
},
setRadius: async (radius: number) => {
await input.fill(radius.toString());
},
};
},
get threshold() {
const button = page.getByTestId('Threshold-btn');
const input = page.locator(`css=div[data-cy="threshold-radius"] input`);
return {
button,
input,
click: async () => {
await button.click();
},
setRadius: async (radius: number) => {
await input.fill(radius.toString());
},
};
},
},
};
}
get noToolsSegmentationPanel() {
const page = this.page;
const panel = this.segmentationPanel;
const menuButton = page.getByTestId(/^panelSegmentation.*-btn$/).first();
return {
menuButton,
panel,
select: async () => {
await menuButton.click();
},
};
}
get tmtvPanel() {
const page = this.page;
return {
get addSegmentationButton() {
const button = page.getByTestId('addSegmentation');
return {
button,
click: async () => {
await button.click();
},
};
},
async exportTmtvCsvReport() {
await page.getByTestId('exportTmtvCsvReport').click();
},
tools: {
get brush() {
const button = page.getByTestId('Brush-btn');
const input = page.locator(`css=div[data-cy="brush-radius"] input`);
return {
button,
input,
click: async () => {
await button.click();
},
setRadius: async (radius: number) => {
await input.fill(radius.toString());
},
};
},
get rectangleROIThreshold() {
const button = page.getByTestId('RectangleROIStartEndThreshold-btn');
const input = page.getByTestId('percentage-of-max-suv-input');
return {
button,
click: async () => {
await button.click();
},
getPercentageOfMaxSUV: async () => {
return await input.inputValue();
},
setPercentageOfMaxSUV: async (percentage: string) => {
await input.fill(percentage);
},
};
},
},
};
}
}

View File

@ -1,10 +1,13 @@
import { Locator, Page } from '@playwright/test'; import { Locator, Page } from '@playwright/test';
import { import {
getMousePosition,
simulateClicksOnElement, simulateClicksOnElement,
simulateDoubleClickOnElement, simulateDoubleClickOnElement,
simulateNormalizedClicksOnElement, simulateNormalizedClicksOnElement,
simulateNormalizedDragOnElement, simulateNormalizedDragOnElement,
} from '../utils'; } from '../utils';
import { DataOverlayPageObject } from './DataOverlayPageObject';
import { DOMOverlayPageObject } from './DOMOverlayPageObject';
type SvgInnerElement = 'circle' | 'path' | 'd'; type SvgInnerElement = 'circle' | 'path' | 'd';
@ -15,11 +18,18 @@ type NormalizedDragParams = {
}; };
export interface IViewportPageObject { export interface IViewportPageObject {
doubleClickAt: (point: { x: number; y: number }) => Promise<void>; nthAnnotation(nth: number): {
locator: Locator;
click: () => Promise<void>;
contextMenu: {
open: () => Promise<void>;
};
};
clickAt: ( clickAt: (
points: { x: number; y: number }[], points: { x: number; y: number }[],
button?: 'left' | 'right' | 'middle' button?: 'left' | 'right' | 'middle'
) => Promise<void>; ) => Promise<void>;
doubleClickAt: (point: { x: number; y: number }) => Promise<void>;
normalizedClickAt: ( normalizedClickAt: (
normalizedPoints: { x: number; y: number }[], normalizedPoints: { x: number; y: number }[],
button?: 'left' | 'right' | 'middle' button?: 'left' | 'right' | 'middle'
@ -38,10 +48,7 @@ export interface IViewportPageObject {
bottomRight: Locator; bottomRight: Locator;
}; };
overlayMenu: { overlayMenu: {
dataOverlay: { dataOverlay: DataOverlayPageObject;
button: Locator;
click: () => Promise<void>;
};
orientation: { orientation: {
button: Locator; button: Locator;
click: () => Promise<void>; click: () => Promise<void>;
@ -57,9 +64,29 @@ export interface IViewportPageObject {
export class ViewportPageObject { export class ViewportPageObject {
readonly page: Page; readonly page: Page;
private readonly dataOverlayPageObject: DataOverlayPageObject;
constructor(page: Page) { constructor(page: Page) {
this.page = page; this.page = page;
this.dataOverlayPageObject = new DataOverlayPageObject(page);
}
private getAnnotation(viewport: Locator, nth: number) {
const page = this.page;
const domOverlayPageObject = new DOMOverlayPageObject(page);
const annotation = viewport.locator('g[data-annotation-uid]').nth(nth);
return {
locator: annotation,
click: async () => {
await annotation.click();
},
contextMenu: {
open: async () => {
await domOverlayPageObject.viewport.annotationContextMenu.open(annotation);
},
},
};
} }
private getOrientationMarkers(viewport: Locator) { private getOrientationMarkers(viewport: Locator) {
@ -82,15 +109,7 @@ export class ViewportPageObject {
private getOverlayMenu(viewport: Locator) { private getOverlayMenu(viewport: Locator) {
return { return {
get dataOverlay() { dataOverlay: this.dataOverlayPageObject,
const button = viewport.locator('[data-cy^="dataOverlayMenu"]').first();
return {
button,
async click() {
await button.click();
},
};
},
get orientation() { get orientation() {
const button = viewport.locator('[data-cy^="orientationMenu"]'); const button = viewport.locator('[data-cy^="orientationMenu"]');
return { return {
@ -118,6 +137,7 @@ export class ViewportPageObject {
private viewportPageObjectFactory(viewport: Locator): IViewportPageObject { private viewportPageObjectFactory(viewport: Locator): IViewportPageObject {
return { return {
nthAnnotation: (nth: number) => this.getAnnotation(viewport, nth),
doubleClickAt: async (point: { x: number; y: number }) => { doubleClickAt: async (point: { x: number; y: number }) => {
await simulateDoubleClickOnElement({ await simulateDoubleClickOnElement({
locator: viewport, locator: viewport,
@ -166,6 +186,70 @@ export class ViewportPageObject {
return this.viewportPageObjectFactory(viewport); return this.viewportPageObjectFactory(viewport);
} }
get crosshairs() {
const page = this.page;
async function increaseSlabThickness(locator: Locator, lineNumber: number, axis: string) {
const lineLocator = locator.locator('line').nth(lineNumber);
await lineLocator.click({ force: true });
await lineLocator.hover({ force: true });
const circleLocator = locator.locator('rect').first();
await circleLocator.hover({ force: true });
await page.mouse.down();
const position = await getMousePosition(page);
switch (axis) {
case 'x':
await page.mouse.move(position.x + 100, position.y);
break;
case 'y':
await page.mouse.move(position.x, position.y + 100);
break;
}
await page.mouse.up();
}
async function rotateCrosshairs(locator: Locator, lineNumber: number) {
const lineLocator = locator.locator('line').nth(lineNumber);
await lineLocator.click({ force: true });
await lineLocator.hover({ force: true });
const circleLocator = locator.locator('circle').nth(1);
await circleLocator.hover({ force: true });
await page.mouse.down();
const position = await getMousePosition(page);
await page.mouse.move(position.x, position.y + 100);
await page.mouse.up();
}
function crosshairsFactory(
locator: Locator,
increaseLineNumber: number,
increaseAxis: 'x' | 'y',
rotateLineNumber: number
) {
return {
increase: async () => {
await increaseSlabThickness(locator, increaseLineNumber, increaseAxis);
},
locator,
rotate: () => {
return rotateCrosshairs(locator, rotateLineNumber);
},
};
}
return {
axial: crosshairsFactory(page.locator('#svg-layer-mpr-axial'), 0, 'x', 3),
sagittal: crosshairsFactory(page.locator('#svg-layer-mpr-sagittal'), 2, 'x', 0),
coronal: crosshairsFactory(page.locator('#svg-layer-mpr-coronal'), 0, 'y', 0),
};
}
async getAll(): Promise<IViewportPageObject[]> { async getAll(): Promise<IViewportPageObject[]> {
const viewports = await this.page.getByTestId('viewport-pane').all(); const viewports = await this.page.getByTestId('viewport-pane').all();
return viewports.map(viewport => this.viewportPageObjectFactory(viewport)); return viewports.map(viewport => this.viewportPageObjectFactory(viewport));

View File

@ -1,4 +1,13 @@
import { DOMOverlayPageObject } from './DOMOverlayPageObject';
import { MainToolbarPageObject } from './MainToolbarPageObject'; import { MainToolbarPageObject } from './MainToolbarPageObject';
import { LeftPanelPageObject } from './LeftPanelPageObject';
import { RightPanelPageObject } from './RightPanelPageObject';
import { ViewportPageObject } from './ViewportPageObject'; import { ViewportPageObject } from './ViewportPageObject';
export { MainToolbarPageObject, ViewportPageObject }; export {
DOMOverlayPageObject,
MainToolbarPageObject,
LeftPanelPageObject,
RightPanelPageObject,
ViewportPageObject,
};

View File

@ -1,5 +1,7 @@
import { type Page, expect } from '@playwright/test'; import { type Page, expect } from '@playwright/test';
import { MainToolbarPageObject, ViewportPageObject } from '../pages';
/** /**
* Defines the optional parameters for the addLengthMeasurement function. * Defines the optional parameters for the addLengthMeasurement function.
*/ */
@ -22,13 +24,16 @@ export async function addLengthMeasurement(
page: Page, page: Page,
options: AddLengthMeasurementOptions = {} options: AddLengthMeasurementOptions = {}
): Promise<void> { ): Promise<void> {
const mainToolbarPageObject = new MainToolbarPageObject(page);
const viewportPageObject = new ViewportPageObject(page);
// Set default values for the click coordinates // Set default values for the click coordinates
const { firstClick = [150, 100], secondClick = [130, 170] } = options; const { firstClick = [150, 100], secondClick = [130, 170] } = options;
const [x1, y1] = firstClick; const [x1, y1] = firstClick;
const [x2, y2] = secondClick; const [x2, y2] = secondClick;
const lengthButton = page.getByTestId('MeasurementTools-split-button-primary'); const lengthButton = mainToolbarPageObject.measurementTools.selectedTool.button;
const viewport = page.locator('.cornerstone-viewport-element').first(); const viewport = viewportPageObject.active;
// Assert that the primary measurement button is 'Length' tool // Assert that the primary measurement button is 'Length' tool
await expect(lengthButton).toHaveAttribute('data-tool', 'Length'); await expect(lengthButton).toHaveAttribute('data-tool', 'Length');
@ -43,10 +48,10 @@ export async function addLengthMeasurement(
await expect(lengthButton).toHaveAttribute('data-active', 'true'); await expect(lengthButton).toHaveAttribute('data-active', 'true');
// Perform the two clicks on the viewport canvas to draw the line // Perform the two clicks on the viewport canvas to draw the line
await viewport.click({ position: { x: x1, y: y1 } }); await viewport.clickAt([{ x: x1, y: y1 }]);
// Delay to allow canvas actions to finish/update // Delay to allow canvas actions to finish/update
await page.waitForTimeout(200); await page.waitForTimeout(200);
await viewport.click({ position: { x: x2, y: y2 } }); await viewport.clickAt([{ x: x2, y: y2 }]);
} }

View File

@ -1,6 +1,8 @@
import { Page } from '@playwright/test'; import { Page } from '@playwright/test';
import { expect } from 'playwright-test-coverage'; import { expect } from 'playwright-test-coverage';
import { DOMOverlayPageObject } from '../pages';
/** /**
* Asserts the number of Modality Load Badges present on the page. * Asserts the number of Modality Load Badges present on the page.
*/ */
@ -11,5 +13,8 @@ export async function assertNumberOfModalityLoadBadges({
page: Page; page: Page;
expectedCount: number; expectedCount: number;
}) { }) {
await expect(page.locator('css=div[data-cy^="ModalityLoadBadge-"]')).toHaveCount(expectedCount); const domOverlayPageObject = new DOMOverlayPageObject(page);
const count = await domOverlayPageObject.viewport.getModalityLoadBadgeCount();
expect(count).toBe(expectedCount);
} }

View File

@ -1,15 +1,33 @@
import { test as base } from 'playwright-test-coverage'; import { test as base } from 'playwright-test-coverage';
import { MainToolbarPageObject, ViewportPageObject } from '../pages'; import {
DOMOverlayPageObject,
MainToolbarPageObject,
LeftPanelPageObject,
RightPanelPageObject,
ViewportPageObject,
} from '../pages';
type PageObjects = { type PageObjects = {
DOMOverlayPageObject: DOMOverlayPageObject;
mainToolbarPageObject: MainToolbarPageObject; mainToolbarPageObject: MainToolbarPageObject;
leftPanelPageObject: LeftPanelPageObject;
rightPanelPageObject: RightPanelPageObject;
viewportPageObject: ViewportPageObject; viewportPageObject: ViewportPageObject;
}; };
export const test = base.extend<PageObjects>({ export const test = base.extend<PageObjects>({
DOMOverlayPageObject: async ({ page }, use) => {
await use(new DOMOverlayPageObject(page));
},
mainToolbarPageObject: async ({ page }, use) => { mainToolbarPageObject: async ({ page }, use) => {
await use(new MainToolbarPageObject(page)); await use(new MainToolbarPageObject(page));
}, },
leftPanelPageObject: async ({ page }, use) => {
await use(new LeftPanelPageObject(page));
},
rightPanelPageObject: async ({ page }, use) => {
await use(new RightPanelPageObject(page));
},
viewportPageObject: async ({ page }, use) => { viewportPageObject: async ({ page }, use) => {
await use(new ViewportPageObject(page)); await use(new ViewportPageObject(page));
}, },