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>
<FooterAction className="mt-4">
<FooterAction.Right>
<FooterAction.Secondary onClick={hide}>Cancel</FooterAction.Secondary>
<FooterAction.Secondary
dataCY="untracked-series-modal-cancel-button"
onClick={hide}
>
Cancel
</FooterAction.Secondary>
<FooterAction.Primary
dataCY="untracked-series-modal-confirm-button"
onClick={() => {
onConfirm();
hide();

View File

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

View File

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

View File

@ -88,7 +88,12 @@ const Thumbnail = ({
loadingProgress && loadingProgress < 1 && 'bg-primary/25'
)}
></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>
{/* top right */}
@ -139,7 +144,10 @@ const Thumbnail = ({
<Tooltip>
<TooltipContent>{description}</TooltipContent>
<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}
</div>
</TooltipTrigger>
@ -180,11 +188,19 @@ const Thumbnail = ({
></div>
<div className="flex h-full w-[calc(100%-12px)] flex-col justify-start">
<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>
<TooltipContent>{description}</TooltipContent>
<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}
</div>
</TooltipTrigger>

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the angle tool', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
@ -17,6 +18,6 @@ test('should display the angle tool', async ({
{ x: 450, y: 250 },
{ 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);
});

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 ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
await page.getByTestId('trackedMeasurements-btn').click();
await rightPanelPageObject.measurementsPanel.select();
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 },
]);
await page.getByTestId('dialog-input').fill('Ringo Starr was the drummer for The Beatles');
await page.getByTestId('input-dialog-save-button').click();
await DOMOverlayPageObject.dialog.input.fillAndSave(
'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);
@ -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 page.getByTestId('dialog-input').fill('Neil Peart was the drummer for Rush');
await page.getByTestId('input-dialog-save-button').click();
await DOMOverlayPageObject.dialog.input.fillAndSave('Neil Peart was the drummer for Rush');
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.
await page.getByTestId('actionsMenuTrigger').click();
await page.getByTestId('Rename').click();
await page.getByTestId('dialog-input').fill('Drummer annotation arrow');
await page.getByTestId('input-dialog-save-button').click();
await rightPanelPageObject.measurementsPanel.panel
.nthMeasurement(0)
.actions.rename('Drummer annotation arrow');
await page.waitForTimeout(2000);

View File

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

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the circle tool', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
@ -16,6 +17,6 @@ test('should display the circle tool', async ({
{ x: 480, y: 205 },
{ 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);
});

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the cobb angle tool', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
@ -18,6 +19,6 @@ test('should display the cobb angle tool', async ({
{ x: 527, y: 293 },
{ 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);
});

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 ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
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 },
]);
await page.getByTestId('prompt-begin-tracking-yes-btn').click();
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
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 ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
const mode = 'viewer';
await visitStudy(page, studyInstanceUID, mode, 2000);
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
// Wait for the segmentation to be loaded.
await page.waitForTimeout(5000);
await page.getByTestId('yes-hydrate-btn').click();
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for the segmentation to hydrate.
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 ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
const mode = 'segmentation';
await visitStudy(page, studyInstanceUID, mode, 2000);
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
// Wait for the segmentation to be loaded.
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.
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 ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
const mode = 'segmentation';
await visitStudy(page, studyInstanceUID, mode, 2000);
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
// Wait for the segmentation to be loaded.
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.
await page.waitForTimeout(5000);

View File

@ -1,44 +1,11 @@
import { Page } from '@playwright/test';
import {
checkForScreenshot,
getMousePosition,
initializeMousePositionTracker,
screenShotPaths,
test,
visitStudy,
} 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 }) => {
const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.1706.8374.643249677828306008300337414785';
const mode = 'viewer';
@ -49,7 +16,7 @@ test.beforeEach(async ({ page }) => {
test.describe('Crosshairs Test', async () => {
test('should render the crosshairs correctly.', async ({ page, mainToolbarPageObject }) => {
await mainToolbarPageObject.layoutSelection.MPR.click();
await page.getByTestId('Crosshairs').click();
await mainToolbarPageObject.crosshairs.click();
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 ({
page,
mainToolbarPageObject,
viewportPageObject,
}) => {
await mainToolbarPageObject.layoutSelection.MPR.click();
await page.getByTestId('Crosshairs').click();
await mainToolbarPageObject.crosshairs.click();
await rotateCrosshairs(page, '#svg-layer-mpr-axial', 3);
await rotateCrosshairs(page, '#svg-layer-mpr-sagittal', 0);
await rotateCrosshairs(page, '#svg-layer-mpr-coronal', 0);
await viewportPageObject.crosshairs.axial.rotate();
await viewportPageObject.crosshairs.sagittal.rotate();
await viewportPageObject.crosshairs.coronal.rotate();
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 ({
page,
mainToolbarPageObject,
viewportPageObject,
}) => {
await mainToolbarPageObject.layoutSelection.MPR.click();
await page.getByTestId('Crosshairs').click();
await mainToolbarPageObject.crosshairs.click();
await increaseSlabThickness(page, '#svg-layer-mpr-axial', 0, 'x');
await increaseSlabThickness(page, '#svg-layer-mpr-sagittal', 2, 'x');
await increaseSlabThickness(page, '#svg-layer-mpr-coronal', 0, 'y');
await viewportPageObject.crosshairs.axial.increase();
await viewportPageObject.crosshairs.sagittal.increase();
await viewportPageObject.crosshairs.coronal.increase();
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 ({
page,
mainToolbarPageObject,
viewportPageObject,
}) => {
await mainToolbarPageObject.layoutSelection.MPR.click();
await page.getByTestId('Crosshairs').click();
await mainToolbarPageObject.crosshairs.click();
await rotateCrosshairs(page, '#svg-layer-mpr-axial', 3);
await rotateCrosshairs(page, '#svg-layer-mpr-sagittal', 0);
await rotateCrosshairs(page, '#svg-layer-mpr-coronal', 0);
await viewportPageObject.crosshairs.axial.rotate();
await viewportPageObject.crosshairs.sagittal.rotate();
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);
});
@ -101,15 +71,17 @@ test.describe('Crosshairs Test', async () => {
test('should reset the crosshairs when a new displayset is loaded', async ({
page,
mainToolbarPageObject,
leftPanelPageObject,
viewportPageObject,
}) => {
await mainToolbarPageObject.layoutSelection.MPR.click();
await page.getByTestId('Crosshairs').click();
await mainToolbarPageObject.crosshairs.click();
await rotateCrosshairs(page, '#svg-layer-mpr-axial', 0);
await rotateCrosshairs(page, '#svg-layer-mpr-sagittal', 0);
await rotateCrosshairs(page, '#svg-layer-mpr-coronal', 3);
await viewportPageObject.crosshairs.axial.rotate();
await viewportPageObject.crosshairs.sagittal.rotate();
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);
});

View File

@ -10,10 +10,9 @@ test('should display added, selected and removed segmentation promptly', async (
await visitStudy(page, studyInstanceUID, mode, 2000);
// Add a segmentation overlay and ensure the overlay menu reflects this change.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await page.getByTestId('AddSegmentationDataOverlay-default').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('2d-tta_nnU-Net_Segmentation').click();
const dataOverlayPageObject = viewportPageObject.getById('default').overlayMenu.dataOverlay;
await dataOverlayPageObject.toggle();
await dataOverlayPageObject.addSegmentation('2d-tta_nnU-Net_Segmentation');
await page.waitForTimeout(5000);
@ -24,7 +23,7 @@ test('should display added, selected and removed segmentation promptly', async (
});
// 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
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.
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.
await page.getByTestId('overlay-ds-select-value-2D-TTA_NNU-NET_SEGMENTATION').click();
await page.getByTestId('Segmentation-SEG').click();
await dataOverlayPageObject.changeSegmentation('2d-tta_nnU-Net_Segmentation', 'Segmentation');
await page.waitForTimeout(5000);
@ -51,7 +49,7 @@ test('should display added, selected and removed segmentation promptly', async (
});
// 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
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.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await dataOverlayPageObject.toggle();
// Remove the segmentation overlay and ensure the overlay menu reflects this change.
await page.getByTestId('overlay-ds-more-button-SEGMENTATION').click();
await page.getByTestId('overlay-ds-remove-button-SEGMENTATION').click();
await dataOverlayPageObject.remove('SEGMENTATION');
await page.waitForTimeout(5000);
@ -78,7 +75,7 @@ test('should display added, selected and removed segmentation promptly', async (
});
// 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
await press({ page, key: 'ArrowDown', nTimes: 50 });

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the ellipse tool', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
@ -16,6 +17,6 @@ test('should display the ellipse tool', async ({
{ x: 446, y: 245 },
{ 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);
});

View File

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

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the length tool', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
@ -16,6 +17,6 @@ test('should display the length tool', async ({
{ x: 364, y: 234 },
{ 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);
});

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the livewire tool', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
@ -20,6 +21,6 @@ test('should display the livewire tool', async ({
{ x: 482, y: 493 },
{ 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);
});

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.
await viewportPageObject.getById('mpr-sagittal').pane.hover();
await viewportPageObject.getById('mpr-sagittal').overlayMenu.dataOverlay.click();
await page.getByTestId('AddSegmentationDataOverlay-mpr-sagittal').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('ARIA RadOnc Structure Sets').click();
const dataOverlayPageObject = viewportPageObject.getById('mpr-sagittal').overlayMenu.dataOverlay;
await dataOverlayPageObject.toggle('mpr-sagittal');
await dataOverlayPageObject.addSegmentation('ARIA RadOnc Structure Sets', 'mpr-sagittal');
// 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.
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.
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 ({
page,
rightPanelPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
await page.getByTestId('side-panel-header-right').click();
await rightPanelPageObject.toggle();
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.
await viewportPageObject.getById('mpr-sagittal').pane.hover();
await viewportPageObject.getById('mpr-sagittal').overlayMenu.dataOverlay.click();
await page.getByTestId('AddSegmentationDataOverlay-mpr-sagittal').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('Segmentation').click();
await viewportPageObject.getById('mpr-axial').pane.hover();
const dataOverlayPageObject = viewportPageObject.getById('mpr-axial').overlayMenu.dataOverlay;
await dataOverlayPageObject.toggle('mpr-axial');
await dataOverlayPageObject.addSegmentation('Segmentation', 'mpr-axial');
// 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.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
await assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
await page.waitForTimeout(5000);

View File

@ -7,136 +7,114 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('checks if Measurements right panel can be hidden/displayed', async ({ page }) => {
const measurementsPanel = page.getByTestId('trackedMeasurements-panel').last();
const rightCollapseBtn = page.getByTestId('side-panel-header-right');
// No data-cy exists in this panel, using Segmentation header button
const segmentationPanel = page.getByRole('button', { name: 'Segmentations' });
const measurementsBtn = page.getByTestId('trackedMeasurements-btn');
const segmentationsBtn = page.getByTestId('panelSegmentation-btn');
test('checks if Measurements right panel can be hidden/displayed', async ({
rightPanelPageObject,
}) => {
const measurementsPanel = rightPanelPageObject.measurementsPanel.panel.locator;
const segmentationPanel = rightPanelPageObject.labelMapSegmentationPanel.panel.locator;
// Assert the measurements panel and segmentation panel is hidden initially
await expect(measurementsPanel).toBeHidden();
await expect(segmentationPanel).toBeHidden();
// 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
await expect(segmentationPanel).toBeVisible();
// Switch to the measurements tab
await measurementsBtn.click();
await rightPanelPageObject.measurementsPanel.select();
// Assert the measurements panel is visible, and segmentation invisible
await expect(measurementsPanel).toBeVisible();
await expect(segmentationPanel).toBeHidden();
// Switch back to segmentations panel
await segmentationsBtn.click();
await rightPanelPageObject.noToolsSegmentationPanel.select();
// Assert the segmentations panel is now visible, measurements panel invisible
await expect(segmentationPanel).toBeVisible();
await expect(measurementsPanel).toBeHidden();
// Click the collapse button to hide the panel
await rightCollapseBtn.click();
await rightPanelPageObject.toggle();
// Assert the measurements and segmentation panel is now hidden
await expect(measurementsPanel).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 measurementsBtn = page.getByTestId('trackedMeasurements-btn');
// Add measurement
await addLengthMeasurement(page);
const viewportNotification = page.getByTestId('viewport-notification');
const viewportNotification = DOMOverlayPageObject.viewport.measurementTracking.locator;
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
await measurementsBtn.click();
const measurementRow = page.getByTestId('data-row').first();
await rightPanelPageObject.measurementsPanel.select();
const measurementRow = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).locator;
await expect(measurementRow).toContainText('(empty)');
// Expand and click rename
const actionsButton = measurementRow.getByRole('button', { name: 'Actions' });
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();
await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).actions.rename(relabelText);
// Check dialog closed and renamed
await expect(renameDialog).toBeHidden();
await expect(DOMOverlayPageObject.dialog.input.locator).toBeHidden();
await expect(measurementRow).toContainText(relabelText);
});
test('checks if measurement item can be relabeled through the context menu on the viewport', async ({
page,
DOMOverlayPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
const relabelText = 'Relabel 12345';
const measurementsBtn = page.getByTestId('trackedMeasurements-btn');
// Add measurement
await addLengthMeasurement(page);
const viewportNotification = page.getByTestId('viewport-notification');
const viewportNotification = DOMOverlayPageObject.viewport.measurementTracking.locator;
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
await measurementsBtn.click();
const measurementRow = page.getByTestId('data-row').first();
await rightPanelPageObject.measurementsPanel.select();
const measurementRow = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).locator;
await expect(measurementRow).toContainText('(empty)');
// Right click and click rename
await page.waitForTimeout(200); // small delay for context menu
const measurementAnnotation = page.locator('g[data-annotation-uid]').first();
await measurementAnnotation.click({ button: 'right', force: true });
await viewportPageObject.active.nthAnnotation(0).contextMenu.open();
await page.waitForTimeout(200); // small delay for context menu
const addLabelButton = page.getByTestId('context-menu-item').filter({ hasText: 'Add Label' });
await expect(addLabelButton).toBeVisible();
const addLabelButton = DOMOverlayPageObject.viewport.annotationContextMenu.addLabel;
await expect(addLabelButton.locator).toBeVisible();
await addLabelButton.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();
await expect(DOMOverlayPageObject.dialog.title).toHaveText('Edit Measurement Label');
await DOMOverlayPageObject.dialog.input.fillAndSave(relabelText);
// Check dialog closed and renamed
await expect(renameDialog).toBeHidden();
await expect(DOMOverlayPageObject.dialog.title).toBeHidden();
await expect(measurementRow).toContainText(relabelText);
});
test('checks if image would jump when clicked on a measurement item', async ({
page,
DOMOverlayPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
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 addLengthMeasurement(page);
const viewportNotification = page.getByTestId('viewport-notification');
await expect(viewportNotification).toBeVisible();
await page.getByTestId('prompt-begin-tracking-yes-btn').click();
await expect(DOMOverlayPageObject.viewport.measurementTracking.locator).toBeVisible();
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
// Change to slice 2
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);
// Open measurement panel and click first measurement
const measurementsBtn = page.getByTestId('trackedMeasurements-btn');
await measurementsBtn.click();
const measurementRow = page.getByTestId('data-row').first();
await measurementRow.click();
await rightPanelPageObject.measurementsPanel.select();
await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
// Confirm jumped to slice 1
await expect(viewportInfoBottomRight).toContainText('1/', { timeout: 10000 });
await expect(viewportInfoBottomRight).not.toContainText('2/');
});
test('checks if measurement item can be deleted under Measurements panel', async ({ page }) => {
const measurementsBtn = page.getByTestId('trackedMeasurements-btn');
test('checks if measurement item can be deleted under Measurements panel', async ({
page,
DOMOverlayPageObject,
rightPanelPageObject,
}) => {
// Add 3 measurements
await addLengthMeasurement(page);
const viewportNotification = page.getByTestId('viewport-notification');
await expect(viewportNotification).toBeVisible();
await page.getByTestId('prompt-begin-tracking-yes-btn').click();
await expect(DOMOverlayPageObject.viewport.measurementTracking.locator).toBeVisible();
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await addLengthMeasurement(page, { firstClick: [170, 100], secondClick: [150, 170] });
await addLengthMeasurement(page, { firstClick: [190, 100], secondClick: [170, 170] });
// Open measurement panel, confirm 3 measurements
await measurementsBtn.click();
await expect(page.getByTestId('data-row')).toHaveCount(3);
await rightPanelPageObject.measurementsPanel.select();
expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(3);
// Delete from measurement
const measurementRow = page.getByTestId('data-row').first();
const actionsButton = measurementRow.getByRole('button', { name: 'Actions' });
await actionsButton.click();
const menuDeleteButton = page.getByRole('menuitem', { name: 'Delete' });
await menuDeleteButton.click();
await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).actions.delete();
await page.waitForTimeout(200);
// 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
const deleteButton = page.getByRole('button', { name: 'Delete' });
deleteButton.click();
await rightPanelPageObject.measurementsPanel.panel.deleteAll();
// Interact with dialog
const untrackDialog = page.getByRole('dialog', { name: 'Untrack Study' });
const untrackButton = untrackDialog.getByRole('button', { name: 'Untrack' });
await expect(DOMOverlayPageObject.dialog.title).toHaveText('Untrack Study');
await expect(untrackDialog).toBeVisible();
await expect(untrackButton).toBeEnabled();
await expect(DOMOverlayPageObject.dialog.confirmation.confirm.button).toBeEnabled();
await untrackButton.click();
await DOMOverlayPageObject.dialog.confirmation.confirm.click();
// Check dialog closed and measurements gone
await expect(untrackDialog).toBeHidden();
await expect(page.getByTestId('data-row')).toHaveCount(0);
await expect(DOMOverlayPageObject.dialog.title).toBeHidden();
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');
});
test('checks if measurement item can be deleted through the context menu on the viewport', async ({
page,
DOMOverlayPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
const measurementsBtn = page.getByTestId('trackedMeasurements-btn');
// Add measurement
await addLengthMeasurement(page);
const viewportNotification = page.getByTestId('viewport-notification');
await expect(viewportNotification).toBeVisible();
await page.getByTestId('prompt-begin-tracking-yes-btn').click();
await expect(DOMOverlayPageObject.viewport.measurementTracking.locator).toBeVisible();
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
// Right click and click rename
await page.waitForTimeout(200); // small delay for context menu
const measurementAnnotation = page.locator('g[data-annotation-uid]').first();
await measurementAnnotation.click({ button: 'right', force: true });
await viewportPageObject.active.nthAnnotation(0).contextMenu.open();
await page.waitForTimeout(200); // small delay for context menu
const deleteButton = page.getByTestId('context-menu-item').filter({ hasText: 'Delete' });
await expect(deleteButton).toBeVisible();
const deleteButton = DOMOverlayPageObject.viewport.annotationContextMenu.delete;
await expect(deleteButton.locator).toBeVisible();
await deleteButton.click();
// Open measurement panel and confirm measurement is gone
await measurementsBtn.click();
await expect(measurementAnnotation).toBeHidden();
await expect(page.getByTestId('data-row')).toHaveCount(0);
await rightPanelPageObject.measurementsPanel.select();
await expect(viewportPageObject.active.nthAnnotation(0).locator).toBeHidden();
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 ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
await page.getByTestId('side-panel-header-right').click();
await rightPanelPageObject.toggle();
// Add multiple segmentation overlays and ensure the overlay menu reflects this change.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await page.getByTestId('AddSegmentationDataOverlay-default').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('2d-tta_nnU-Net_Segmentation').click();
const dataOverlayPageObject = viewportPageObject.getById('default').overlayMenu.dataOverlay;
await dataOverlayPageObject.toggle();
await dataOverlayPageObject.addSegmentation('2d-tta_nnU-Net_Segmentation');
// A short wait after each overlay is selected to ensure it loads.
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.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
await page.getByTestId('AddSegmentationDataOverlay-default').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('Segmentation').click();
await dataOverlayPageObject.addSegmentation('Segmentation');
// A short wait after each overlay is selected to ensure it loads.
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.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
await page.getByTestId('AddSegmentationDataOverlay-default').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('3d_lowres-tta_nnU-Net_Segmentation').click();
await dataOverlayPageObject.addSegmentation('3d_lowres-tta_nnU-Net_Segmentation');
// A short wait after each overlay is selected to ensure it loads.
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.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); // hide
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); // show
await dataOverlayPageObject.toggle(); // hide
await dataOverlayPageObject.toggle(); // show
await checkForScreenshot({
page,
screenshotPath: screenShotPaths.multipleSegmentationDataOverlays.threeSegOverlaysInOverlayMenu,
});
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); // hide
await dataOverlayPageObject.toggle(); // hide
// Navigate to image 56.
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
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await dataOverlayPageObject.toggle();
await page.getByTestId('AddSegmentationDataOverlay-default').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('Series 3 - RTSTRUCT').click();
await dataOverlayPageObject.addSegmentation('Series 3 - RTSTRUCT');
// A short wait after each overlay is selected to ensure it loads.
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.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); // hide
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click(); // show
await dataOverlayPageObject.toggle(); // hide
await dataOverlayPageObject.toggle(); // show
await checkForScreenshot({
page,

View File

@ -8,11 +8,12 @@ test.beforeEach(async ({ page }) => {
test('should display the probe tool', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
await mainToolbarPageObject.moreTools.probe.click();
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);
});

View File

@ -12,16 +12,15 @@ test('should overlay an unhydrated RTSTRUCT over a display set that the RTSTRUCT
page,
viewportPageObject,
}) => {
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await page.getByTestId('AddSegmentationDataOverlay-default').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('Contours on PET').click();
const dataOverlayPageObject = viewportPageObject.getById('default').overlayMenu.dataOverlay;
await dataOverlayPageObject.toggle();
await dataOverlayPageObject.addSegmentation('Contours on PET');
// Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
// Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await dataOverlayPageObject.toggle();
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 ({
page,
mainToolbarPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
await page.getByTestId('side-panel-header-right').click();
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await page.getByTestId('AddSegmentationDataOverlay-default').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('ARIA RadOnc Structure Sets').click();
await rightPanelPageObject.toggle();
const dataOverlayPageObject = viewportPageObject.getById('default').overlayMenu.dataOverlay;
await dataOverlayPageObject.toggle();
await dataOverlayPageObject.addSegmentation('ARIA RadOnc Structure Sets');
// Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
// Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await dataOverlayPageObject.toggle();
await page.waitForTimeout(5000);

View File

@ -1,5 +1,4 @@
import { test } from 'playwright-test-coverage';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
test.beforeEach(async ({ page }) => {
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);
});
test('should hydrate RT reports correctly', async ({ page }) => {
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
test('should hydrate RT reports correctly', async ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
await page.waitForTimeout(5000);
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 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 checkForScreenshot(page, page, screenShotPaths.rtHydration.rtJumpToStructure);
});

View File

@ -1,5 +1,4 @@
import { test } from 'playwright-test-coverage';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
test.beforeEach(async ({ page }) => {
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);
});
test('should hydrate RT reports correctly', async ({ page }) => {
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
test('should hydrate RT reports correctly', async ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
await checkForScreenshot(page, page, screenShotPaths.rtHydration2.rtPreHydration);
// wait for 3 seconds
@ -34,9 +38,7 @@ test('should hydrate RT reports correctly', async ({ page }) => {
await page.waitForTimeout(3000);
//
// 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);
});

View File

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

View File

@ -6,11 +6,17 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should hydrate an RTSTRUCT and then launch MPR', async ({ page, mainToolbarPageObject }) => {
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
test('should hydrate an RTSTRUCT and then launch MPR', async ({
page,
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);

View File

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

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the rectangle tool', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
@ -16,6 +17,6 @@ test('should display the rectangle tool', async ({
{ x: 476, y: 159 },
{ 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);
});

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 ({
page,
leftPanelPageObject,
viewportPageObject,
}) => {
await page.getByTestId('study-browser-thumbnail').nth(2).dblclick();
await leftPanelPageObject.loadSeriesByDescription('Apparent Diffusion Coefficient');
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await page.getByTestId('AddSegmentationDataOverlay-default').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('T2 Weighted Axial Segmentations').click();
await viewportPageObject.getById('default').overlayMenu.dataOverlay.toggle();
await viewportPageObject
.getById('default')
.overlayMenu.dataOverlay.addSegmentation('T2 Weighted Axial Segmentations');
// Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
// Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await viewportPageObject.getById('default').overlayMenu.dataOverlay.toggle();
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 ({
page,
mainToolbarPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
await page.getByTestId('side-panel-header-right').click();
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await page.getByTestId('AddSegmentationDataOverlay-default').click();
await page.getByText('SELECT A SEGMENTATION').click();
await page.getByTestId('Segmentation').click();
await rightPanelPageObject.toggle();
await viewportPageObject.getById('default').overlayMenu.dataOverlay.toggle();
await viewportPageObject
.getById('default')
.overlayMenu.dataOverlay.addSegmentation('Segmentation');
// Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
// Hide the overlay menu.
await viewportPageObject.getById('default').overlayMenu.dataOverlay.click();
await viewportPageObject.getById('default').overlayMenu.dataOverlay.toggle();
await page.waitForTimeout(5000);

View File

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

View File

@ -1,5 +1,4 @@
import { test } from 'playwright-test-coverage';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
test.beforeEach(async ({ page }) => {
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);
});
test('should hydrate SEG reports correctly', async ({ page }) => {
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
test('should hydrate SEG reports correctly', async ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await leftPanelPageObject.loadSeriesByDescription('SEG');
await page.waitForTimeout(5000);
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 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('should properly hydrate SEG from 3D four up layout', async ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
mainToolbarPageObject,
}) => {
await mainToolbarPageObject.layoutSelection.threeDFourUp.click();
@ -30,7 +32,7 @@ test.describe('3D four up SEG hydration', async () => {
screenShotPaths.segHydrationFrom3DFourUp.threeDFourUpBeforeSEG
);
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await leftPanelPageObject.loadSeriesByDescription('SEG');
await page.waitForTimeout(5000);
await checkForScreenshot(
@ -39,7 +41,7 @@ test.describe('3D four up SEG hydration', async () => {
screenShotPaths.segHydrationFrom3DFourUp.threeDFourUpAfterSEG
);
await page.getByTestId('yes-hydrate-btn').click();
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(5000);
await checkForScreenshot(

View File

@ -6,20 +6,26 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should properly display MPR for MR', async ({ page, mainToolbarPageObject }) => {
await page.getByTestId('side-panel-header-right').click();
test('should properly display MPR for MR', async ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
mainToolbarPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await mainToolbarPageObject.layoutSelection.MPR.click();
await page.waitForTimeout(5000);
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 checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSEG);
await page.getByTestId('yes-hydrate-btn').click();
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(5000);
await checkForScreenshot(page, page, screenShotPaths.segHydrationFromMPR.mprAfterSegHydrated);

View File

@ -6,11 +6,17 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should properly display MPR for MR', async ({ page, mainToolbarPageObject }) => {
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
test('should properly display MPR for MR', async ({
page,
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 checkForScreenshot(page, page, screenShotPaths.segHydrationThenMPR.segPostHydration);

View File

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

View File

@ -1,5 +1,4 @@
import { test } from 'playwright-test-coverage';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
test.beforeEach(async ({ page }) => {
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);
});
test('should hydrate SR reports correctly', async ({ page }) => {
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('trackedMeasurements-btn').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
test('should hydrate SR reports correctly', async ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
}) => {
await rightPanelPageObject.toggle();
await rightPanelPageObject.measurementsPanel.select();
await leftPanelPageObject.loadSeriesByModality('SR');
await page.waitForTimeout(2000);
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 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);
});

View File

@ -21,17 +21,20 @@ test.beforeEach(async ({ page }) => {
test('should hydrate SCOORD3D probe measurements correctly', async ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
// Wait for the side panel to be visible and clickable
await page.waitForTimeout(3000);
// Navigate to the tracked measurements panel
await page.getByTestId('side-panel-header-right').click({ timeout: 15000 });
await page.getByTestId('trackedMeasurements-btn').click();
await rightPanelPageObject.toggle();
await rightPanelPageObject.measurementsPanel.select();
// 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);
// 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);
// 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
await page.getByTestId('yes-hydrate-btn').click();
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for hydration to complete and rendering to stabilize
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
const measurementRows = page.getByTestId('data-row');
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)
for (let i = 0; i < rowCount; i++) {
const row = measurementRows.nth(i);
const rowText = await row.textContent();
const measurementText = await rightPanelPageObject.measurementsPanel.panel
.nthMeasurement(i)
.locator.textContent();
// 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
@ -117,7 +126,7 @@ test('should hydrate SCOORD3D probe measurements correctly', async ({
});
// 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
await checkForScreenshot(
@ -129,20 +138,26 @@ test('should hydrate SCOORD3D probe measurements correctly', async ({
test('should display SCOORD3D probe measurements correctly', async ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
// Wait for the side panel to be visible and clickable
await page.waitForTimeout(3000);
// First hydrate the SR to load the measurements
await page.getByTestId('side-panel-header-right').click({ timeout: 15000 });
await page.getByTestId('trackedMeasurements-btn').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await rightPanelPageObject.toggle();
await rightPanelPageObject.measurementsPanel.select();
await leftPanelPageObject.loadSeriesByModality('SR');
await page.waitForTimeout(2000);
// Wait for the hydrate button to be visible and clickable
await page.getByTestId('yes-hydrate-btn').waitFor({ state: 'visible', timeout: 15000 });
await page.getByTestId('yes-hydrate-btn').click();
await DOMOverlayPageObject.viewport.segmentationHydration.yes.button.waitFor({
state: 'visible',
timeout: 15000,
});
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(2000);
// 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
const measurementRows = page.getByTestId('data-row');
const rowCount = await measurementRows.count();
const rowCount = await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount();
expect(rowCount).toBeGreaterThan(0);
// Verify that the measurements are probe measurements (not other types like rectangle)
for (let i = 0; i < rowCount; i++) {
const row = measurementRows.nth(i);
const rowText = await row.textContent();
const measurementText = await rightPanelPageObject.measurementsPanel.panel
.nthMeasurement(i)
.locator.textContent();
// 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 ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
// Wait for the side panel to be visible and clickable
await page.waitForTimeout(3000);
// Navigate to the tracked measurements panel
await page.getByTestId('side-panel-header-right').click({ timeout: 15000 });
await page.getByTestId('trackedMeasurements-btn').click();
await rightPanelPageObject.toggle();
await rightPanelPageObject.measurementsPanel.select();
// 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);
// 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);
// 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
await page.getByTestId('yes-hydrate-btn').click();
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for hydration to complete and rendering to stabilize
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
const measurementRows = page.getByTestId('data-row');
const rowCount = await measurementRows.count();
const rowCount = await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount();
expect(rowCount).toBeGreaterThan(0);
// Verify that the measurements are rectangle measurements (not other types)
for (let i = 0; i < rowCount; i++) {
const row = measurementRows.nth(i);
const rowText = await row.textContent();
const measurementText = await rightPanelPageObject.measurementsPanel.panel
.nthMeasurement(i)
.locator.textContent();
// 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
@ -118,7 +124,7 @@ test('should hydrate SCOORD rectangle measurements correctly', async ({
});
// 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
await checkForScreenshot(
@ -130,20 +136,26 @@ test('should hydrate SCOORD rectangle measurements correctly', async ({
test('should display SCOORD rectangle measurements correctly', async ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
// Wait for the side panel to be visible and clickable
await page.waitForTimeout(3000);
// First hydrate the SR to load the measurements
await page.getByTestId('side-panel-header-right').click({ timeout: 15000 });
await page.getByTestId('trackedMeasurements-btn').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await rightPanelPageObject.toggle();
await rightPanelPageObject.measurementsPanel.select();
await leftPanelPageObject.loadSeriesByModality('SR');
await page.waitForTimeout(2000);
// Wait for the hydrate button to be visible and clickable
await page.getByTestId('yes-hydrate-btn').waitFor({ state: 'visible', timeout: 15000 });
await page.getByTestId('yes-hydrate-btn').click();
await DOMOverlayPageObject.viewport.segmentationHydration.yes.button.waitFor({
state: 'visible',
timeout: 15000,
});
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await page.waitForTimeout(2000);
// 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
const measurementRows = page.getByTestId('data-row');
const rowCount = await measurementRows.count();
const rowCount = await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount();
expect(rowCount).toBeGreaterThan(0);
// Verify that the measurements are rectangle measurements (not other types like probe)
for (let i = 0; i < rowCount; i++) {
const row = measurementRows.nth(i);
const rowText = await row.textContent();
const measurementText = await rightPanelPageObject.measurementsPanel.panel
.nthMeasurement(i)
.locator.textContent();
// 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);
});
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
const segmentationPanel = page.getByTestId('panelSegmentationWithToolsLabelMap-btn');
const segmentationPanel = rightPanelPageObject.labelMapSegmentationPanel.menuButton;
await expect(segmentationPanel).toBeVisible();
// Switch to labelmap tab.
segmentationPanel.click();
// Add segmentation
const addSegmentationBtn = page.getByTestId('addSegmentation');
await addSegmentationBtn.click();
await rightPanelPageObject.labelMapSegmentationPanel.addSegmentationButton.click();
// Expect new segmentation and blank segment named "Segment 1"
await expect(page.getByTestId('data-row')).toHaveCount(1);
await expect(page.getByTestId('data-row')).toContainText('Segment 1');
const segment1 = rightPanelPageObject.labelMapSegmentationPanel.panel.nthSegmentation(0);
expect(await rightPanelPageObject.labelMapSegmentationPanel.panel.getSegmentationCount()).toBe(1);
await expect(segment1.locator).toContainText('Segment 1');
// Rename
const segment1Dropdown = page
.getByTestId('data-row')
.first()
.getByRole('button', { name: 'Actions' });
await segment1Dropdown.click();
await segment1.actions.rename('Segment One');
const renameButton = page.getByRole('menuitem', { name: 'Rename' });
await expect(renameButton).toBeVisible();
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');
await expect(segment1.locator).toContainText('Segment One');
await expect(segment1.locator).not.toContainText('Segment 1');
// Delete
await segment1Dropdown.click();
const deleteButton = page.getByRole('menuitem', { name: 'Delete' });
await expect(deleteButton).toBeVisible();
await deleteButton.click();
await segment1.actions.delete();
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 ({
page,
DOMOverlayPageObject,
leftPanelPageObject,
rightPanelPageObject,
viewportPageObject,
}) => {
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 });
// Add Segmentations
const segmentationsThumbnail = page
.getByTestId('study-browser-thumbnail-no-image')
.getByRole('button', { name: 'Segmentation' });
await segmentationsThumbnail.dblclick();
await leftPanelPageObject.loadSeriesByModality('SEG');
await page.waitForTimeout(3000);
// Confirm open segmentation
const viewportNotification = page.getByTestId('viewport-notification');
await expect(viewportNotification).toBeVisible();
await page.getByTestId('yes-hydrate-btn').click();
await expect(DOMOverlayPageObject.viewport.segmentationHydration.locator).toBeVisible();
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Segmentation Panel should already be open
const segmentationPanel = page.getByTestId('panelSegmentationWithToolsLabelMap-btn');
const segmentationPanel = rightPanelPageObject.labelMapSegmentationPanel.menuButton;
await expect(segmentationPanel).toBeVisible();
// Confirm spleen jumps to slice 17
// First iteration repeat to account for segmentation loading delays
const spleenRow = page.getByTestId('data-row').filter({ hasText: 'Spleen' });
await expect(async () => {
await spleenRow.click();
await rightPanelPageObject.labelMapSegmentationPanel.panel.segmentationByText('Spleen').click();
await expect(viewportInfoBottomRight).toContainText('17/');
}).toPass({
timeout: 10000,
});
// Esophagus - 5
const esophagusRow = page.getByTestId('data-row').filter({ hasText: 'Esophagus' });
await esophagusRow.click();
await rightPanelPageObject.labelMapSegmentationPanel.panel
.segmentationByText('Esophagus')
.click();
await expect(viewportInfoBottomRight).toContainText('5/');
// Pancreas - 22
const pancreasRow = page.getByTestId('data-row').filter({ hasText: 'Pancreas' });
await pancreasRow.click();
await rightPanelPageObject.labelMapSegmentationPanel.panel.segmentationByText('Pancreas').click();
await expect(viewportInfoBottomRight).toContainText('22/');
});

View File

@ -8,6 +8,7 @@ test.beforeEach(async ({ page }) => {
test('should display the spline tool', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
@ -20,6 +21,6 @@ test('should display the spline tool', async ({
{ x: 482, y: 493 },
{ 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);
});

View File

@ -3,21 +3,22 @@ import { downloadAsString } from './utils/download';
test('should create and download the TMTV CSV report correctly', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501';
const mode = 'tmtv';
await visitStudy(page, studyInstanceUID, mode, 10000);
await page.getByTestId('addSegmentation').click();
await page.getByTestId('Brush-btn').click();
await rightPanelPageObject.tmtvPanel.addSegmentationButton.click();
await rightPanelPageObject.tmtvPanel.tools.brush.click();
await viewportPageObject.getById('ctAXIAL').normalizedClickAt([{ x: 0.5, y: 0.5 }]);
await page.waitForTimeout(5000);
const downloadPromise = page.waitForEvent('download');
await page.getByTestId('exportTmtvCsvReport').click();
await rightPanelPageObject.tmtvPanel.exportTmtvCsvReport();
const download = await downloadPromise;
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 ({
page,
leftPanelPageObject,
mainToolbarPageObject,
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);
// Show sidebar
await page.getByTestId('side-panel-header-left').click();
await leftPanelPageObject.toggle();
// Change to image where SUV cannot be calculated
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 ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
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 page.getByTestId('addSegmentation').click();
await page.getByTestId('RectangleROIStartEndThreshold-btn').click();
await rightPanelPageObject.tmtvPanel.addSegmentationButton.click();
await page.getByTestId('percentage-of-max-suv-input').fill('0');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0');
const rectangleROIThreshold = rightPanelPageObject.tmtvPanel.tools.rectangleROIThreshold;
await rectangleROIThreshold.click();
await page.getByTestId('percentage-of-max-suv-input').fill('0.27');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.27');
await rectangleROIThreshold.setPercentageOfMaxSUV('0');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0');
await page.getByTestId('percentage-of-max-suv-input').fill('0.9467');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.9467');
await rectangleROIThreshold.setPercentageOfMaxSUV('0.27');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.27');
await page.getByTestId('percentage-of-max-suv-input').fill('.');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.');
await rectangleROIThreshold.setPercentageOfMaxSUV('0.9467');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.9467');
await page.getByTestId('percentage-of-max-suv-input').fill('1');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('1');
await rectangleROIThreshold.setPercentageOfMaxSUV('.');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.');
await page.getByTestId('percentage-of-max-suv-input').fill('1.1');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('1');
await rectangleROIThreshold.setPercentageOfMaxSUV('1');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('1');
await page.getByTestId('percentage-of-max-suv-input').fill('1806');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('1');
await rectangleROIThreshold.setPercentageOfMaxSUV('1.1');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('1');
await page.getByTestId('percentage-of-max-suv-input').fill('');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('');
await rectangleROIThreshold.setPercentageOfMaxSUV('1806');
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
// 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');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.275');
await rectangleROIThreshold.setPercentageOfMaxSUV('9');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.275');
await page.getByTestId('percentage-of-max-suv-input').fill('-678');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.275');
await rectangleROIThreshold.setPercentageOfMaxSUV('-678');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.275');
await page.getByTestId('percentage-of-max-suv-input').fill('+');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.275');
await rectangleROIThreshold.setPercentageOfMaxSUV('+');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.275');
await page.getByTestId('percentage-of-max-suv-input').fill('-');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.275');
await rectangleROIThreshold.setPercentageOfMaxSUV('-');
expect(await rectangleROIThreshold.getPercentageOfMaxSUV()).toBe('0.275');
await page.getByTestId('percentage-of-max-suv-input').fill('e');
expect(await page.getByTestId('percentage-of-max-suv-input').inputValue()).toBe('0.275');
await rectangleROIThreshold.setPercentageOfMaxSUV('e');
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
await expandedStudyRow.waitFor({ state: 'visible', timeout: 10000 });
await expandedStudyRow.scrollIntoViewIfNeeded();
// Wait for content to load and stabilize, including any lazy-loaded items
await page.waitForTimeout(2000);
// Wait for network to be idle to ensure all content is loaded
await page.waitForLoadState('networkidle');
// Additional wait to ensure scrollbar rendering is stable
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;
}
get crosshairs() {
const button = this.page.getByTestId('Crosshairs');
return {
button,
async click() {
await button.click();
},
};
}
get layoutSelection() {
const page = this.page;
@ -96,6 +105,15 @@ export class MainToolbarPageObject {
return {
...measurementTools,
get selectedTool() {
const button = page.getByTestId('MeasurementTools-split-button-primary');
return {
button,
async click() {
await button.click();
},
};
},
get arrowAnnotate() {
const button = page.getByTestId('ArrowAnnotate');
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 {
getMousePosition,
simulateClicksOnElement,
simulateDoubleClickOnElement,
simulateNormalizedClicksOnElement,
simulateNormalizedDragOnElement,
} from '../utils';
import { DataOverlayPageObject } from './DataOverlayPageObject';
import { DOMOverlayPageObject } from './DOMOverlayPageObject';
type SvgInnerElement = 'circle' | 'path' | 'd';
@ -15,11 +18,18 @@ type NormalizedDragParams = {
};
export interface IViewportPageObject {
doubleClickAt: (point: { x: number; y: number }) => Promise<void>;
nthAnnotation(nth: number): {
locator: Locator;
click: () => Promise<void>;
contextMenu: {
open: () => Promise<void>;
};
};
clickAt: (
points: { x: number; y: number }[],
button?: 'left' | 'right' | 'middle'
) => Promise<void>;
doubleClickAt: (point: { x: number; y: number }) => Promise<void>;
normalizedClickAt: (
normalizedPoints: { x: number; y: number }[],
button?: 'left' | 'right' | 'middle'
@ -38,10 +48,7 @@ export interface IViewportPageObject {
bottomRight: Locator;
};
overlayMenu: {
dataOverlay: {
button: Locator;
click: () => Promise<void>;
};
dataOverlay: DataOverlayPageObject;
orientation: {
button: Locator;
click: () => Promise<void>;
@ -57,9 +64,29 @@ export interface IViewportPageObject {
export class ViewportPageObject {
readonly page: Page;
private readonly dataOverlayPageObject: DataOverlayPageObject;
constructor(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) {
@ -82,15 +109,7 @@ export class ViewportPageObject {
private getOverlayMenu(viewport: Locator) {
return {
get dataOverlay() {
const button = viewport.locator('[data-cy^="dataOverlayMenu"]').first();
return {
button,
async click() {
await button.click();
},
};
},
dataOverlay: this.dataOverlayPageObject,
get orientation() {
const button = viewport.locator('[data-cy^="orientationMenu"]');
return {
@ -118,6 +137,7 @@ export class ViewportPageObject {
private viewportPageObjectFactory(viewport: Locator): IViewportPageObject {
return {
nthAnnotation: (nth: number) => this.getAnnotation(viewport, nth),
doubleClickAt: async (point: { x: number; y: number }) => {
await simulateDoubleClickOnElement({
locator: viewport,
@ -166,6 +186,70 @@ export class ViewportPageObject {
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[]> {
const viewports = await this.page.getByTestId('viewport-pane').all();
return viewports.map(viewport => this.viewportPageObjectFactory(viewport));

View File

@ -1,4 +1,13 @@
import { DOMOverlayPageObject } from './DOMOverlayPageObject';
import { MainToolbarPageObject } from './MainToolbarPageObject';
import { LeftPanelPageObject } from './LeftPanelPageObject';
import { RightPanelPageObject } from './RightPanelPageObject';
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 { MainToolbarPageObject, ViewportPageObject } from '../pages';
/**
* Defines the optional parameters for the addLengthMeasurement function.
*/
@ -22,13 +24,16 @@ export async function addLengthMeasurement(
page: Page,
options: AddLengthMeasurementOptions = {}
): Promise<void> {
const mainToolbarPageObject = new MainToolbarPageObject(page);
const viewportPageObject = new ViewportPageObject(page);
// Set default values for the click coordinates
const { firstClick = [150, 100], secondClick = [130, 170] } = options;
const [x1, y1] = firstClick;
const [x2, y2] = secondClick;
const lengthButton = page.getByTestId('MeasurementTools-split-button-primary');
const viewport = page.locator('.cornerstone-viewport-element').first();
const lengthButton = mainToolbarPageObject.measurementTools.selectedTool.button;
const viewport = viewportPageObject.active;
// Assert that the primary measurement button is 'Length' tool
await expect(lengthButton).toHaveAttribute('data-tool', 'Length');
@ -43,10 +48,10 @@ export async function addLengthMeasurement(
await expect(lengthButton).toHaveAttribute('data-active', 'true');
// 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
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 { expect } from 'playwright-test-coverage';
import { DOMOverlayPageObject } from '../pages';
/**
* Asserts the number of Modality Load Badges present on the page.
*/
@ -11,5 +13,8 @@ export async function assertNumberOfModalityLoadBadges({
page: Page;
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 { MainToolbarPageObject, ViewportPageObject } from '../pages';
import {
DOMOverlayPageObject,
MainToolbarPageObject,
LeftPanelPageObject,
RightPanelPageObject,
ViewportPageObject,
} from '../pages';
type PageObjects = {
DOMOverlayPageObject: DOMOverlayPageObject;
mainToolbarPageObject: MainToolbarPageObject;
leftPanelPageObject: LeftPanelPageObject;
rightPanelPageObject: RightPanelPageObject;
viewportPageObject: ViewportPageObject;
};
export const test = base.extend<PageObjects>({
DOMOverlayPageObject: async ({ page }, use) => {
await use(new DOMOverlayPageObject(page));
},
mainToolbarPageObject: async ({ page }, use) => {
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) => {
await use(new ViewportPageObject(page));
},