From d917e74e6888744c9a5f3fa2170993c23a9b9341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Alves=20de=20Faria=20Resende?= Date: Fri, 12 Dec 2025 13:46:45 -0300 Subject: [PATCH] tests(Playwright): Full implementation of Page Object Models (POMs) to Playwright tests (#5608) --- .../untrackSeriesModal.tsx | 8 +- .../src/components/DataRow/DataRow.tsx | 1 + .../components/FooterAction/FooterAction.tsx | 23 +- .../src/components/Thumbnail/Thumbnail.tsx | 24 +- tests/Angle.spec.ts | 3 +- tests/ArrowAnnotate.spec.ts | 22 +- tests/Bidirectional.spec.ts | 3 +- tests/Circle.spec.ts | 3 +- tests/CobbAngle.spec.ts | 3 +- tests/ContextMenu.spec.ts | 3 +- tests/ContourSegLocking.spec.ts | 27 +- tests/Crosshairs.spec.ts | 76 ++--- tests/DataOverlayMenu.spec.ts | 23 +- tests/Ellipse.spec.ts | 3 +- tests/JumpToMeasurementMPR.spec.ts | 24 +- tests/LabelMapSegLocking.spec.ts | 26 +- tests/Length.spec.ts | 3 +- tests/Livewire.spec.ts | 3 +- tests/MPRThenRTOverlayNoHydration.spec.ts | 11 +- tests/MPRThenSEGOverlayNoHydration.spec.ts | 16 +- tests/MeasurementPanel.spec.ts | 172 +++++------ .../MultipleSegmentationDataOverlays.spec.ts | 35 +-- tests/Probe.spec.ts | 3 +- ...rUnreferencedDisplaySetNoHydration.spec.ts | 9 +- tests/RTDataOverlayNoHydrationThenMPR.spec.ts | 12 +- tests/RTHydration.spec.ts | 20 +- tests/RTHydration2.spec.ts | 18 +- tests/RTHydrationFromMPR.spec.ts | 14 +- tests/RTHydrationThenMPR.spec.ts | 14 +- tests/RTNoHydrationThenMPR.spec.ts | 11 +- tests/Rectangle.spec.ts | 3 +- ...rUnreferencedDisplaySetNoHydration.spec.ts | 13 +- .../SEGDataOverlayNoHydrationThenMPR.spec.ts | 13 +- tests/SEGDrawingToolsResizing.spec.ts | 34 ++- tests/SEGHydration.spec.ts | 16 +- tests/SEGHydrationFrom3DFourUp.spec.ts | 6 +- tests/SEGHydrationFromMPR.spec.ts | 14 +- tests/SEGHydrationThenMPR.spec.ts | 14 +- tests/SEGNoHydrationThenMPR.spec.ts | 11 +- tests/SRHydration.spec.ts | 20 +- tests/Scoord3dProbe.spec.ts | 55 ++-- tests/ScoordRectangle.spec.ts | 54 ++-- tests/SegmentationPanel.spec.ts | 67 ++--- tests/Spline.spec.ts | 3 +- tests/TMTVCSVReport.spec.ts | 7 +- tests/TMTVModalityUnit.spec.ts | 3 +- tests/TMTVSUV.spec.ts | 61 ++-- tests/Worklist.spec.ts | 6 +- tests/pages/DOMOverlayPageObject.ts | 134 +++++++++ tests/pages/DataOverlayPageObject.ts | 29 ++ tests/pages/LeftPanelPageObject.ts | 35 +++ tests/pages/MainToolbarPageObject.ts | 18 ++ tests/pages/RightPanelPageObject.ts | 271 ++++++++++++++++++ tests/pages/ViewportPageObject.ts | 112 +++++++- tests/pages/index.ts | 11 +- tests/utils/addLengthMeasurement.ts | 13 +- tests/utils/assertions.ts | 7 +- tests/utils/fixture.ts | 20 +- 58 files changed, 1167 insertions(+), 496 deletions(-) create mode 100644 tests/pages/DOMOverlayPageObject.ts create mode 100644 tests/pages/DataOverlayPageObject.ts create mode 100644 tests/pages/LeftPanelPageObject.ts create mode 100644 tests/pages/RightPanelPageObject.ts diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/untrackSeriesModal.tsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/untrackSeriesModal.tsx index c7b0809a3..4a123d942 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/untrackSeriesModal.tsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/untrackSeriesModal.tsx @@ -12,8 +12,14 @@ export function UntrackSeriesModal({ hide, onConfirm, message }) { - Cancel + + Cancel + { onConfirm(); hide(); diff --git a/platform/ui-next/src/components/DataRow/DataRow.tsx b/platform/ui-next/src/components/DataRow/DataRow.tsx index 1e374e651..05151ee46 100644 --- a/platform/ui-next/src/components/DataRow/DataRow.tsx +++ b/platform/ui-next/src/components/DataRow/DataRow.tsx @@ -315,6 +315,7 @@ const DataRowComponent = React.forwardRef( 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); diff --git a/platform/ui-next/src/components/FooterAction/FooterAction.tsx b/platform/ui-next/src/components/FooterAction/FooterAction.tsx index 052dc466f..11f417559 100644 --- a/platform/ui-next/src/components/FooterAction/FooterAction.tsx +++ b/platform/ui-next/src/components/FooterAction/FooterAction.tsx @@ -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 ( @@ -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 ( @@ -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 ( diff --git a/platform/ui-next/src/components/Thumbnail/Thumbnail.tsx b/platform/ui-next/src/components/Thumbnail/Thumbnail.tsx index 192aca7dd..8a97aba1c 100644 --- a/platform/ui-next/src/components/Thumbnail/Thumbnail.tsx +++ b/platform/ui-next/src/components/Thumbnail/Thumbnail.tsx @@ -88,7 +88,12 @@ const Thumbnail = ({ loadingProgress && loadingProgress < 1 && 'bg-primary/25' )} > -
{modality}
+
+ {modality} +
{/* top right */} @@ -139,7 +144,10 @@ const Thumbnail = ({ {description} -
+
{description}
@@ -180,11 +188,19 @@ const Thumbnail = ({ >
-
{modality}
+
+ {modality} +
{description} -
+
{description}
diff --git a/tests/Angle.spec.ts b/tests/Angle.spec.ts index d92f9e293..4b188115c 100644 --- a/tests/Angle.spec.ts +++ b/tests/Angle.spec.ts @@ -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); }); diff --git a/tests/ArrowAnnotate.spec.ts b/tests/ArrowAnnotate.spec.ts index a7be02a55..f329d0e22 100644 --- a/tests/ArrowAnnotate.spec.ts +++ b/tests/ArrowAnnotate.spec.ts @@ -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); diff --git a/tests/Bidirectional.spec.ts b/tests/Bidirectional.spec.ts index 293a8df13..7a5322e80 100644 --- a/tests/Bidirectional.spec.ts +++ b/tests/Bidirectional.spec.ts @@ -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, diff --git a/tests/Circle.spec.ts b/tests/Circle.spec.ts index d475d7af2..8f59e0e45 100644 --- a/tests/Circle.spec.ts +++ b/tests/Circle.spec.ts @@ -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); }); diff --git a/tests/CobbAngle.spec.ts b/tests/CobbAngle.spec.ts index 012fb4580..d880d7b9f 100644 --- a/tests/CobbAngle.spec.ts +++ b/tests/CobbAngle.spec.ts @@ -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); }); diff --git a/tests/ContextMenu.spec.ts b/tests/ContextMenu.spec.ts index 1327cec71..9cd760a59 100644 --- a/tests/ContextMenu.spec.ts +++ b/tests/ContextMenu.spec.ts @@ -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); diff --git a/tests/ContourSegLocking.spec.ts b/tests/ContourSegLocking.spec.ts index 7eacd1339..f9b8d367b 100644 --- a/tests/ContourSegLocking.spec.ts +++ b/tests/ContourSegLocking.spec.ts @@ -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); diff --git a/tests/Crosshairs.spec.ts b/tests/Crosshairs.spec.ts index 003ec80d7..022cce47b 100644 --- a/tests/Crosshairs.spec.ts +++ b/tests/Crosshairs.spec.ts @@ -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); }); diff --git a/tests/DataOverlayMenu.spec.ts b/tests/DataOverlayMenu.spec.ts index 649d90811..71f115f20 100644 --- a/tests/DataOverlayMenu.spec.ts +++ b/tests/DataOverlayMenu.spec.ts @@ -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 }); diff --git a/tests/Ellipse.spec.ts b/tests/Ellipse.spec.ts index bec7ecc57..15580dd5c 100644 --- a/tests/Ellipse.spec.ts +++ b/tests/Ellipse.spec.ts @@ -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); }); diff --git a/tests/JumpToMeasurementMPR.spec.ts b/tests/JumpToMeasurementMPR.spec.ts index 4935b78e2..3f0dcc649 100644 --- a/tests/JumpToMeasurementMPR.spec.ts +++ b/tests/JumpToMeasurementMPR.spec.ts @@ -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, diff --git a/tests/LabelMapSegLocking.spec.ts b/tests/LabelMapSegLocking.spec.ts index c2ddb603e..e35c11d74 100644 --- a/tests/LabelMapSegLocking.spec.ts +++ b/tests/LabelMapSegLocking.spec.ts @@ -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({ diff --git a/tests/Length.spec.ts b/tests/Length.spec.ts index 3124c6e5a..43f262cb9 100644 --- a/tests/Length.spec.ts +++ b/tests/Length.spec.ts @@ -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); }); diff --git a/tests/Livewire.spec.ts b/tests/Livewire.spec.ts index f4f96354c..5eeb32a13 100644 --- a/tests/Livewire.spec.ts +++ b/tests/Livewire.spec.ts @@ -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); }); diff --git a/tests/MPRThenRTOverlayNoHydration.spec.ts b/tests/MPRThenRTOverlayNoHydration.spec.ts index 3a5764d73..2b0e91237 100644 --- a/tests/MPRThenRTOverlayNoHydration.spec.ts +++ b/tests/MPRThenRTOverlayNoHydration.spec.ts @@ -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); diff --git a/tests/MPRThenSEGOverlayNoHydration.spec.ts b/tests/MPRThenSEGOverlayNoHydration.spec.ts index 9cb24a1e6..b67e3d8d9 100644 --- a/tests/MPRThenSEGOverlayNoHydration.spec.ts +++ b/tests/MPRThenSEGOverlayNoHydration.spec.ts @@ -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); diff --git a/tests/MeasurementPanel.spec.ts b/tests/MeasurementPanel.spec.ts index 326dd0905..6b686fa7c 100644 --- a/tests/MeasurementPanel.spec.ts +++ b/tests/MeasurementPanel.spec.ts @@ -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); }); diff --git a/tests/MultipleSegmentationDataOverlays.spec.ts b/tests/MultipleSegmentationDataOverlays.spec.ts index 044d8b3dc..3b57c6ff5 100644 --- a/tests/MultipleSegmentationDataOverlays.spec.ts +++ b/tests/MultipleSegmentationDataOverlays.spec.ts @@ -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, diff --git a/tests/Probe.spec.ts b/tests/Probe.spec.ts index 86eda1baf..f1c6ea81e 100644 --- a/tests/Probe.spec.ts +++ b/tests/Probe.spec.ts @@ -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); }); diff --git a/tests/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts b/tests/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts index ca29d3c3b..c8b5cb97f 100644 --- a/tests/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts +++ b/tests/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts @@ -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); diff --git a/tests/RTDataOverlayNoHydrationThenMPR.spec.ts b/tests/RTDataOverlayNoHydrationThenMPR.spec.ts index e84e24648..07a063f2f 100644 --- a/tests/RTDataOverlayNoHydrationThenMPR.spec.ts +++ b/tests/RTDataOverlayNoHydrationThenMPR.spec.ts @@ -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); diff --git a/tests/RTHydration.spec.ts b/tests/RTHydration.spec.ts index 02254f2d1..7b6d81c58 100644 --- a/tests/RTHydration.spec.ts +++ b/tests/RTHydration.spec.ts @@ -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); }); diff --git a/tests/RTHydration2.spec.ts b/tests/RTHydration2.spec.ts index b040c713d..431f054bc 100644 --- a/tests/RTHydration2.spec.ts +++ b/tests/RTHydration2.spec.ts @@ -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); }); diff --git a/tests/RTHydrationFromMPR.spec.ts b/tests/RTHydrationFromMPR.spec.ts index 3d286a71b..5814dac2e 100644 --- a/tests/RTHydrationFromMPR.spec.ts +++ b/tests/RTHydrationFromMPR.spec.ts @@ -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); diff --git a/tests/RTHydrationThenMPR.spec.ts b/tests/RTHydrationThenMPR.spec.ts index f8d13277f..e8bd33475 100644 --- a/tests/RTHydrationThenMPR.spec.ts +++ b/tests/RTHydrationThenMPR.spec.ts @@ -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); diff --git a/tests/RTNoHydrationThenMPR.spec.ts b/tests/RTNoHydrationThenMPR.spec.ts index cf41f44a6..9d2345002 100644 --- a/tests/RTNoHydrationThenMPR.spec.ts +++ b/tests/RTNoHydrationThenMPR.spec.ts @@ -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); diff --git a/tests/Rectangle.spec.ts b/tests/Rectangle.spec.ts index c32a2dece..34a5b501d 100644 --- a/tests/Rectangle.spec.ts +++ b/tests/Rectangle.spec.ts @@ -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); }); diff --git a/tests/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts b/tests/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts index 0b1b96139..24fa894ce 100644 --- a/tests/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts +++ b/tests/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts @@ -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); diff --git a/tests/SEGDataOverlayNoHydrationThenMPR.spec.ts b/tests/SEGDataOverlayNoHydrationThenMPR.spec.ts index 41c542474..93dcbf1fd 100644 --- a/tests/SEGDataOverlayNoHydrationThenMPR.spec.ts +++ b/tests/SEGDataOverlayNoHydrationThenMPR.spec.ts @@ -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); diff --git a/tests/SEGDrawingToolsResizing.spec.ts b/tests/SEGDrawingToolsResizing.spec.ts index 55deeaa7a..1fe57ce47 100644 --- a/tests/SEGDrawingToolsResizing.spec.ts +++ b/tests/SEGDrawingToolsResizing.spec.ts @@ -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); }); diff --git a/tests/SEGHydration.spec.ts b/tests/SEGHydration.spec.ts index f6abea1cc..069f2808f 100644 --- a/tests/SEGHydration.spec.ts +++ b/tests/SEGHydration.spec.ts @@ -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); diff --git a/tests/SEGHydrationFrom3DFourUp.spec.ts b/tests/SEGHydrationFrom3DFourUp.spec.ts index c2193408b..83dc03d15 100644 --- a/tests/SEGHydrationFrom3DFourUp.spec.ts +++ b/tests/SEGHydrationFrom3DFourUp.spec.ts @@ -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( diff --git a/tests/SEGHydrationFromMPR.spec.ts b/tests/SEGHydrationFromMPR.spec.ts index 4b3ccaa25..bf621978f 100644 --- a/tests/SEGHydrationFromMPR.spec.ts +++ b/tests/SEGHydrationFromMPR.spec.ts @@ -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); diff --git a/tests/SEGHydrationThenMPR.spec.ts b/tests/SEGHydrationThenMPR.spec.ts index dd40fc6f3..acdd5b4a4 100644 --- a/tests/SEGHydrationThenMPR.spec.ts +++ b/tests/SEGHydrationThenMPR.spec.ts @@ -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); diff --git a/tests/SEGNoHydrationThenMPR.spec.ts b/tests/SEGNoHydrationThenMPR.spec.ts index 0e3957026..5bb936728 100644 --- a/tests/SEGNoHydrationThenMPR.spec.ts +++ b/tests/SEGNoHydrationThenMPR.spec.ts @@ -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); diff --git a/tests/SRHydration.spec.ts b/tests/SRHydration.spec.ts index dc2bc0c4d..10408da50 100644 --- a/tests/SRHydration.spec.ts +++ b/tests/SRHydration.spec.ts @@ -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); }); diff --git a/tests/Scoord3dProbe.spec.ts b/tests/Scoord3dProbe.spec.ts index 524d47f78..69cab3b9a 100644 --- a/tests/Scoord3dProbe.spec.ts +++ b/tests/Scoord3dProbe.spec.ts @@ -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(); } }); diff --git a/tests/ScoordRectangle.spec.ts b/tests/ScoordRectangle.spec.ts index 59ecdc00d..836178252 100644 --- a/tests/ScoordRectangle.spec.ts +++ b/tests/ScoordRectangle.spec.ts @@ -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(); } }); diff --git a/tests/SegmentationPanel.spec.ts b/tests/SegmentationPanel.spec.ts index ea715e770..249b01cf3 100644 --- a/tests/SegmentationPanel.spec.ts +++ b/tests/SegmentationPanel.spec.ts @@ -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/'); }); diff --git a/tests/Spline.spec.ts b/tests/Spline.spec.ts index c87493bc3..db57222d8 100644 --- a/tests/Spline.spec.ts +++ b/tests/Spline.spec.ts @@ -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); }); diff --git a/tests/TMTVCSVReport.spec.ts b/tests/TMTVCSVReport.spec.ts index ae26065b6..70b25de0e 100644 --- a/tests/TMTVCSVReport.spec.ts +++ b/tests/TMTVCSVReport.spec.ts @@ -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( diff --git a/tests/TMTVModalityUnit.spec.ts b/tests/TMTVModalityUnit.spec.ts index d80f5e5b7..314da4b3a 100644 --- a/tests/TMTVModalityUnit.spec.ts +++ b/tests/TMTVModalityUnit.spec.ts @@ -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 }]); diff --git a/tests/TMTVSUV.spec.ts b/tests/TMTVSUV.spec.ts index 518dddbba..c823e34a6 100644 --- a/tests/TMTVSUV.spec.ts +++ b/tests/TMTVSUV.spec.ts @@ -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'); }); diff --git a/tests/Worklist.spec.ts b/tests/Worklist.spec.ts index 4c4a02c7a..8c01e1683 100644 --- a/tests/Worklist.spec.ts +++ b/tests/Worklist.spec.ts @@ -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); diff --git a/tests/pages/DOMOverlayPageObject.ts b/tests/pages/DOMOverlayPageObject.ts new file mode 100644 index 000000000..81bb48bac --- /dev/null +++ b/tests/pages/DOMOverlayPageObject.ts @@ -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(); + }, + }; + } +} diff --git a/tests/pages/DataOverlayPageObject.ts b/tests/pages/DataOverlayPageObject.ts new file mode 100644 index 000000000..4b830d6d9 --- /dev/null +++ b/tests/pages/DataOverlayPageObject.ts @@ -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(); + } +} diff --git a/tests/pages/LeftPanelPageObject.ts b/tests/pages/LeftPanelPageObject.ts new file mode 100644 index 000000000..b188dcba7 --- /dev/null +++ b/tests/pages/LeftPanelPageObject.ts @@ -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(); + } +} diff --git a/tests/pages/MainToolbarPageObject.ts b/tests/pages/MainToolbarPageObject.ts index 8422fcc1d..dd5c42e48 100644 --- a/tests/pages/MainToolbarPageObject.ts +++ b/tests/pages/MainToolbarPageObject.ts @@ -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 { diff --git a/tests/pages/RightPanelPageObject.ts b/tests/pages/RightPanelPageObject.ts new file mode 100644 index 000000000..b6e9bd57f --- /dev/null +++ b/tests/pages/RightPanelPageObject.ts @@ -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); + }, + }; + }, + }, + }; + } +} diff --git a/tests/pages/ViewportPageObject.ts b/tests/pages/ViewportPageObject.ts index cb022f826..a5b38dfcb 100644 --- a/tests/pages/ViewportPageObject.ts +++ b/tests/pages/ViewportPageObject.ts @@ -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; + nthAnnotation(nth: number): { + locator: Locator; + click: () => Promise; + contextMenu: { + open: () => Promise; + }; + }; clickAt: ( points: { x: number; y: number }[], button?: 'left' | 'right' | 'middle' ) => Promise; + doubleClickAt: (point: { x: number; y: number }) => Promise; 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; - }; + dataOverlay: DataOverlayPageObject; orientation: { button: Locator; click: () => Promise; @@ -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 { const viewports = await this.page.getByTestId('viewport-pane').all(); return viewports.map(viewport => this.viewportPageObjectFactory(viewport)); diff --git a/tests/pages/index.ts b/tests/pages/index.ts index 383eb03c0..8f441d1c8 100644 --- a/tests/pages/index.ts +++ b/tests/pages/index.ts @@ -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, +}; diff --git a/tests/utils/addLengthMeasurement.ts b/tests/utils/addLengthMeasurement.ts index 28aff8ebd..9d5960a11 100644 --- a/tests/utils/addLengthMeasurement.ts +++ b/tests/utils/addLengthMeasurement.ts @@ -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 { + 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 }]); } diff --git a/tests/utils/assertions.ts b/tests/utils/assertions.ts index ff345e3e1..813d3a4a3 100644 --- a/tests/utils/assertions.ts +++ b/tests/utils/assertions.ts @@ -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); } diff --git a/tests/utils/fixture.ts b/tests/utils/fixture.ts index 1fc467c13..116856a43 100644 --- a/tests/utils/fixture.ts +++ b/tests/utils/fixture.ts @@ -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({ + 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)); },