fix: ohif tests to run with cornerstone 3d 5.0 (#6043)

* chore(tests): Update multiple screenshot test images for various specs

* feat(screenshot-reviewer): Add screenshot review tool and update package.json scripts

* fix(DICOMSRDisplayTool): Improve actor presence check in viewport

* chore(tests): Update multiple screenshot assets for various specs

* chore(tests): Integrate waitForPaintToSettle and waitForViewportsRendered in multiple specs for improved rendering stability

* chore(tests): Update screenshot assets for SEGHydration and SEGNoHydration specs

* test: update progressive loading screenshots

* jest 30 test fixes for compatibility with pnpm cs3d

* Use correct setDisplaySets instead of setDataId

* fix: Naming change for LegacyVolumeViewport3D

* Update to allow tolerance for contour tests

* update

* fix

* refactor: Replace instanceof checks with utility functions for viewport type validation

* fix: Update createSegmentationForViewport to handle undefined displaySetInstanceUID gracefully

* bun lock

* fix: Install cs3d with pnpm instead of bun

* Update node version for playwright

* Update to v5.0.0 of cs3d

* fix: Build dependency

* audit

* Change to a web await retry assert

* Fix timing related test failures

* fix: Freehand close

---------

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
AlirezaandBill Wallace authored and GitHub committed 2026-06-09 20:25:14 -04:00
1 parent a27f779f0b
commit 6dd150d401
172 files changed
+3081 -1866

No files matched your search

+40 -16
View File
@@ -1,4 +1,11 @@
import { expect, test, visitStudy, getSvgAttribute, navigateWithViewportArrow } from './utils';
import {
expect,
test,
visitStudy,
getSvgAttribute,
navigateWithViewportArrow,
waitForViewportsRendered,
} from './utils';
import { expectRowSelected } from './utils/assertions';
const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501';
@@ -20,12 +27,21 @@ test.beforeEach(async ({
});
test('should navigate the contours when clicking each segments in the right panel', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
const getSegment = (index: number) =>
rightPanelPageObject.contourSegmentationPanel.panel.nthSegment(index);
// Clicking a segment navigates the viewport to that segment's slice and
// re-renders its contour. Wait for the render to settle before reading the
// SVG path so we don't capture the previous segment's path on slower CI.
const selectSegment = async (index: number) => {
await getSegment(index).click();
await waitForViewportsRendered(page);
};
const seg0 = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -33,7 +49,7 @@ test('should navigate the contours when clicking each segments in the right pane
});
expect(seg0, 'Segment at index 0: expected a non-null SVG path').not.toBeNull();
await getSegment(3).click();
await selectSegment(3);
const seg3 = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -42,7 +58,7 @@ test('should navigate the contours when clicking each segments in the right pane
expect(seg3, 'Segment at index 3: expected a non-null SVG path').not.toBeNull();
await expectRowSelected(getSegment(3));
await getSegment(2).click();
await selectSegment(2);
const seg2 = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -51,7 +67,7 @@ test('should navigate the contours when clicking each segments in the right pane
expect(seg2, 'Segment at index 2: expected a non-null SVG path').not.toBeNull();
await expectRowSelected(getSegment(2));
await getSegment(1).click();
await selectSegment(1);
const seg1 = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -61,7 +77,7 @@ test('should navigate the contours when clicking each segments in the right pane
await expectRowSelected(getSegment(1));
// Clicking segments again should return the original paths
await getSegment(2).click();
await selectSegment(2);
const seg2Again = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -70,7 +86,7 @@ test('should navigate the contours when clicking each segments in the right pane
expect(seg2Again, 'Segment 2 again: expected to match the original segment 2 path').toBe(seg2);
await expectRowSelected(getSegment(2));
await getSegment(1).click();
await selectSegment(1);
const seg1Again = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -79,7 +95,7 @@ test('should navigate the contours when clicking each segments in the right pane
expect(seg1Again, 'Segment 1 again: expected to match the original segment 1 path').toBe(seg1);
await expectRowSelected(getSegment(1));
await getSegment(0).click();
await selectSegment(0);
const seg0Again = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -88,7 +104,7 @@ test('should navigate the contours when clicking each segments in the right pane
expect(seg0Again, 'Segment 0 again: expected to match the original segment 0 path').toBe(seg0);
await expectRowSelected(getSegment(0));
await getSegment(3).click();
await selectSegment(3);
const seg3Again = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -99,12 +115,20 @@ test('should navigate the contours when clicking each segments in the right pane
});
test('should navigate the segmentations using the Viewport arrow buttons', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
const getSegment = (index: number) =>
rightPanelPageObject.contourSegmentationPanel.panel.nthSegment(index);
// Arrow navigation jumps to the next/prev segment and re-renders its contour.
// Wait for the render to settle before reading the SVG path.
const navigate = async (direction: 'next' | 'prev') => {
await navigateWithViewportArrow(viewportPageObject, direction);
await waitForViewportsRendered(page);
};
const initialSvgPath = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -114,7 +138,7 @@ test('should navigate the segmentations using the Viewport arrow buttons', async
// Expect the correct segment to be selected in the right panel
await expectRowSelected(getSegment(0));
await navigateWithViewportArrow(viewportPageObject, 'next');
await navigate('next');
const secondSvgPath = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -123,7 +147,7 @@ test('should navigate the segmentations using the Viewport arrow buttons', async
expect(secondSvgPath, 'Segment at index 1: expected a different SVG path from segment 0').not.toBe(initialSvgPath);
await expectRowSelected(getSegment(1));
await navigateWithViewportArrow(viewportPageObject, 'next');
await navigate('next');
const thirdSvgPath = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -132,7 +156,7 @@ test('should navigate the segmentations using the Viewport arrow buttons', async
expect(thirdSvgPath, 'Segment at index 2: expected a different SVG path from segment 1').not.toBe(secondSvgPath);
await expectRowSelected(getSegment(2));
await navigateWithViewportArrow(viewportPageObject, 'next');
await navigate('next');
const fourthSvgPath = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -142,7 +166,7 @@ test('should navigate the segmentations using the Viewport arrow buttons', async
await expectRowSelected(getSegment(3));
// Wraparound test — next from last should return to first segment
await navigateWithViewportArrow(viewportPageObject, 'next');
await navigate('next');
const svgPathWraparoundWithNext = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -155,7 +179,7 @@ test('should navigate the segmentations using the Viewport arrow buttons', async
await expectRowSelected(getSegment(0));
// Wraparound test — prev from first should return to last segment
await navigateWithViewportArrow(viewportPageObject, 'prev');
await navigate('prev');
const svgPathWraparoundWithPrev = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -167,7 +191,7 @@ test('should navigate the segmentations using the Viewport arrow buttons', async
).toBe(fourthSvgPath);
await expectRowSelected(getSegment(3));
await navigateWithViewportArrow(viewportPageObject, 'prev');
await navigate('prev');
const backToThirdSvgPath = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -176,7 +200,7 @@ test('should navigate the segmentations using the Viewport arrow buttons', async
expect(backToThirdSvgPath, 'Expected path to match third segment after going prev from fourth').toBe(thirdSvgPath);
await expectRowSelected(getSegment(2));
await navigateWithViewportArrow(viewportPageObject, 'prev');
await navigate('prev');
const backToSecondSvgPath = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
@@ -185,7 +209,7 @@ test('should navigate the segmentations using the Viewport arrow buttons', async
expect(backToSecondSvgPath, 'Expected path to match second segment after going prev from third').toBe(secondSvgPath);
await expectRowSelected(getSegment(1));
await navigateWithViewportArrow(viewportPageObject, 'prev');
await navigate('prev');
const backToFirstSvgPath = await getSvgAttribute({
viewportPageObject,
svgInnerElement: 'path',
+42 -14
View File
@@ -126,8 +126,9 @@ test.describe('Crosshairs Test', async () => {
await viewportPageObject.crosshairs.axial.rotate();
await viewportPageObject.crosshairs.sagittal.rotate();
await viewportPageObject.crosshairs.coronal.rotate();
const afterRotate = await getCrosshairsSignature(page);
expect(afterRotate).not.toEqual(beforeRotate);
await expect
.poll(() => getCrosshairsSignature(page), { timeout: CROSSHAIRS_DOM_TIMEOUT_MS })
.not.toEqual(beforeRotate);
});
test('should allow the user to adjust the slab thickness', async ({
@@ -143,8 +144,9 @@ test.describe('Crosshairs Test', async () => {
await viewportPageObject.crosshairs.axial.increase();
await viewportPageObject.crosshairs.sagittal.increase();
await viewportPageObject.crosshairs.coronal.increase();
const afterIncrease = await getLayerSignature(page, 'svg-layer-mpr-axial');
expect(afterIncrease).not.toEqual(beforeIncrease);
await expect
.poll(() => getLayerSignature(page, 'svg-layer-mpr-axial'), { timeout: CROSSHAIRS_DOM_TIMEOUT_MS })
.not.toEqual(beforeIncrease);
});
test('should reset the crosshairs to the initial position when reset is clicked', async ({
@@ -160,14 +162,27 @@ test.describe('Crosshairs Test', async () => {
await viewportPageObject.crosshairs.axial.rotate();
await viewportPageObject.crosshairs.sagittal.rotate();
await viewportPageObject.crosshairs.coronal.rotate();
const rotatedPosition = await getCrosshairsSignature(page);
expect(rotatedPosition).not.toEqual(initialPosition);
await expect
.poll(() => getCrosshairsSignature(page), { timeout: CROSSHAIRS_DOM_TIMEOUT_MS })
.not.toEqual(initialPosition);
await mainToolbarPageObject.moreTools.reset.click();
await expectCrosshairsReady(page);
expect(await areLayerLinesAxisAligned(page, 'svg-layer-mpr-axial')).toBeTruthy();
expect(await areLayerLinesAxisAligned(page, 'svg-layer-mpr-sagittal')).toBeTruthy();
expect(await areLayerLinesAxisAligned(page, 'svg-layer-mpr-coronal')).toBeTruthy();
await expect
.poll(() => areLayerLinesAxisAligned(page, 'svg-layer-mpr-axial'), {
timeout: CROSSHAIRS_DOM_TIMEOUT_MS,
})
.toBe(true);
await expect
.poll(() => areLayerLinesAxisAligned(page, 'svg-layer-mpr-sagittal'), {
timeout: CROSSHAIRS_DOM_TIMEOUT_MS,
})
.toBe(true);
await expect
.poll(() => areLayerLinesAxisAligned(page, 'svg-layer-mpr-coronal'), {
timeout: CROSSHAIRS_DOM_TIMEOUT_MS,
})
.toBe(true);
});
test('should reset the crosshairs when a new displayset is loaded', async ({
@@ -184,13 +199,26 @@ test.describe('Crosshairs Test', async () => {
await viewportPageObject.crosshairs.axial.rotate();
await viewportPageObject.crosshairs.sagittal.rotate();
await viewportPageObject.crosshairs.coronal.rotate();
const rotatedPosition = await getCrosshairsSignature(page);
expect(rotatedPosition).not.toEqual(initialPosition);
await expect
.poll(() => getCrosshairsSignature(page), { timeout: CROSSHAIRS_DOM_TIMEOUT_MS })
.not.toEqual(initialPosition);
await leftPanelPageObject.loadSeriesByDescription('Recon 3: LIVER 3 PHASE (AP)');
await expectCrosshairsReady(page);
expect(await areLayerLinesAxisAligned(page, 'svg-layer-mpr-axial')).toBeTruthy();
expect(await areLayerLinesAxisAligned(page, 'svg-layer-mpr-sagittal')).toBeTruthy();
expect(await areLayerLinesAxisAligned(page, 'svg-layer-mpr-coronal')).toBeTruthy();
await expect
.poll(() => areLayerLinesAxisAligned(page, 'svg-layer-mpr-axial'), {
timeout: CROSSHAIRS_DOM_TIMEOUT_MS,
})
.toBe(true);
await expect
.poll(() => areLayerLinesAxisAligned(page, 'svg-layer-mpr-sagittal'), {
timeout: CROSSHAIRS_DOM_TIMEOUT_MS,
})
.toBe(true);
await expect
.poll(() => areLayerLinesAxisAligned(page, 'svg-layer-mpr-coronal'), {
timeout: CROSSHAIRS_DOM_TIMEOUT_MS,
})
.toBe(true);
});
});
+14 -1
View File
@@ -59,12 +59,25 @@ test('rectangle and freehand at identical coordinates should yield comparable ar
// ANNOTATION_COMPLETED). Ending at the start point would enter interactive close-
// preview mode and suppress the completion event; ending further away
// would draw a diagonal that distorts the rectangular shape.
//
// The closing gap must be a fixed number of canvas pixels, not a normalized
// fraction: cs3d's close-proximity threshold is in pixels, so a fractional gap
// (0.01 × height) grew past the threshold on taller CI viewports and the
// contour never auto-closed (intermittent "no cachedStats"). Derive it from the
// viewport bbox so the gap is viewport-size-independent.
const CLOSE_GAP_PX = 6;
const viewportBox = await activeViewport.pane.boundingBox();
if (!viewportBox) {
throw new Error('Active viewport bounding box not found');
}
const closeY = 0.3 + CLOSE_GAP_PX / viewportBox.height;
const corners = [
{ x: 0.3, y: 0.3 },
{ x: 0.55, y: 0.3 },
{ x: 0.55, y: 0.55 },
{ x: 0.3, y: 0.55 },
{ x: 0.3, y: 0.31 },
{ x: 0.3, y: closeY },
];
const [topLeft, , bottomRight] = corners;
+17 -2
View File
@@ -1,4 +1,11 @@
import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
import {
checkForScreenshot,
screenShotPaths,
test,
visitStudy,
waitForPaintToSettle,
waitForViewportRenderCycle,
} from './utils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5';
@@ -111,9 +118,17 @@ test('should hydrate in MPR correctly', async ({
screenShotPaths.jumpToMeasurementMPR.jumpInMPR
);
const seriesChangeRenderCycle = waitForViewportRenderCycle(page, { renderedTimeout: 30000 });
await leftPanelPageObject.loadSeriesByDescription('Lung 3.0 CE');
await page.waitForTimeout(5000);
await seriesChangeRenderCycle;
// Series change unloads the old volume and progressively streams the new
// one; the wait helper resolves when loadStatus.loaded flips true, but the
// MPR mappers can still be sampling stale low-res frames for one tick.
// Give the streaming tail a chance to upload before screenshotting.
await page.waitForTimeout(2000);
await waitForPaintToSettle(page);
await checkForScreenshot(
page,
+2 -2
View File
@@ -43,11 +43,11 @@ test('should display the livewire tool', async ({
const stats = livewires[0].firstTargetStats!;
expect(stats.areaUnit).toBe('mm²');
expect(Math.round(stats.area as number)).toBe(28906);
expect(Math.round(stats.area as number)).toBe(30412);
const lines = activeViewport.getSvgAnnotationStatTextLines(livewires[0].annotationUID);
await expect(lines).toHaveCount(1);
await expect(lines.nth(0)).toHaveText('Area: 28906 mm²');
await expect(lines.nth(0)).toHaveText('Area: 30412 mm²');
});
test('should restore viewport interactivity after deleting an in-progress Livewire annotation via context menu', async ({
+8 -8
View File
@@ -168,14 +168,14 @@ test('checks if measurement item can be deleted under Measurements panel', async
// Open measurement panel, confirm 3 measurements
await rightPanelPageObject.measurementsPanel.select();
expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(3);
await expect(rightPanelPageObject.measurementsPanel.panel.rows).toHaveCount(3);
// Delete from measurement
await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).actions.delete();
await page.waitForTimeout(200);
// Confirm one measurement is gone
expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(2);
await expect(rightPanelPageObject.measurementsPanel.panel.rows).toHaveCount(2);
// Delete all measurements via main Measurement Panel delete button and untrack
await rightPanelPageObject.measurementsPanel.panel.deleteAll();
@@ -189,7 +189,7 @@ test('checks if measurement item can be deleted under Measurements panel', async
// Check dialog closed and measurements gone
await expect(DOMOverlayPageObject.dialog.title).toBeHidden();
expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(0);
await expect(rightPanelPageObject.measurementsPanel.panel.rows).toHaveCount(0);
const measurementsPanel = rightPanelPageObject.measurementsPanel.panel.locator;
await expect(measurementsPanel).toContainText('No tracked measurements');
@@ -219,7 +219,7 @@ test('checks if measurement item can be deleted through the context menu on the
// Open measurement panel and confirm measurement is gone
await rightPanelPageObject.measurementsPanel.select();
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(0);
await expect(rightPanelPageObject.measurementsPanel.panel.rows).toHaveCount(0);
});
test('checks that undo after delete-all restores measurements as tracked', async ({
@@ -243,13 +243,13 @@ test('checks that undo after delete-all restores measurements as tracked', async
});
await rightPanelPageObject.measurementsPanel.select();
expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(2);
await expect(rightPanelPageObject.measurementsPanel.panel.rows).toHaveCount(2);
await rightPanelPageObject.measurementsPanel.panel.deleteAll();
await expect(DOMOverlayPageObject.dialog.title).toHaveText('Untrack Study');
await DOMOverlayPageObject.dialog.confirmation.confirm.click();
await expect(DOMOverlayPageObject.dialog.title).toBeHidden();
expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(0);
await expect(rightPanelPageObject.measurementsPanel.panel.rows).toHaveCount(0);
await mainToolbarPageObject.undo.click();
@@ -326,14 +326,14 @@ test.describe('simplified tracking mode', () => {
});
await rightPanelPageObject.measurementsPanel.select();
expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(2);
await expect(rightPanelPageObject.measurementsPanel.panel.rows).toHaveCount(2);
await rightPanelPageObject.measurementsPanel.panel.deleteAll();
await expect(DOMOverlayPageObject.dialog.title).toHaveText('Untrack Study');
await DOMOverlayPageObject.dialog.confirmation.confirm.click();
await expect(DOMOverlayPageObject.dialog.title).toBeHidden();
expect(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBe(0);
await expect(rightPanelPageObject.measurementsPanel.panel.rows).toHaveCount(0);
await mainToolbarPageObject.undo.click();
+13
View File
@@ -5,6 +5,7 @@ import {
screenShotPaths,
test,
visitStudy,
waitForPaintToSettle,
waitForViewportsRendered,
waitForViewportRenderCycle,
expect,
@@ -40,6 +41,12 @@ test.describe('3D four up SEG hydration', async () => {
await leftPanelPageObject.loadSeriesByDescription('SEG');
await viewportRenderCycle;
await expect(DOMOverlayPageObject.viewport.segmentationHydration.locator).toBeVisible({
timeout: 60000,
});
await waitForViewportsRendered(page, { timeout: 60000 });
await page.waitForTimeout(3000);
await waitForPaintToSettle(page);
await checkForScreenshot(
page,
@@ -57,6 +64,12 @@ test.describe('3D four up SEG hydration', async () => {
// Wait until all viewports have finished rendering
await viewportRenderCycle;
// 3D volume rendering keeps streaming refined geometry after the load
// status flips; give the GPU a window to present the final frame before
// screenshotting.
await waitForViewportsRendered(page, { timeout: 180000 });
await page.waitForTimeout(3000);
await waitForPaintToSettle(page);
await checkForScreenshot({
page,
+12
View File
@@ -3,6 +3,7 @@ import {
screenShotPaths,
test,
visitStudy,
waitForPaintToSettle,
waitForViewportRenderCycle,
waitForViewportsRendered,
} from './utils';
@@ -36,6 +37,10 @@ test('should properly display MPR for MR', async ({
await leftPanelPageObject.loadSeriesByDescription('SEG');
await waitForViewportsRendered(page);
// SEG load triggers an additional progressive labelmap upload after the
// viewports first report 'rendered'; let that finish before screenshotting.
await page.waitForTimeout(1500);
await waitForPaintToSettle(page);
await checkForScreenshot(
page,
@@ -49,6 +54,11 @@ test('should properly display MPR for MR', async ({
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
await viewportRenderCycle;
// Hydration propagates the labelmap volume to the sagittal/coronal MPR
// viewports asynchronously; wait for that propagation to render before
// capturing.
await page.waitForTimeout(1500);
await waitForPaintToSettle(page);
await checkForScreenshot(
page,
@@ -61,6 +71,8 @@ test('should properly display MPR for MR', async ({
await mainToolbarPageObject.layoutSelection.axialPrimary.click();
await viewportRenderAfterLayoutChange;
await page.waitForTimeout(1000);
await waitForPaintToSettle(page);
await checkForScreenshot(
page,
+8 -1
View File
@@ -1,4 +1,10 @@
import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
import {
checkForScreenshot,
screenShotPaths,
test,
visitStudy,
waitForPaintToSettle,
} from './utils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046';
@@ -27,6 +33,7 @@ test('should launch MPR with unhydrated SEG', async ({
await mainToolbarPageObject.layoutSelection.MPR.click();
await page.waitForTimeout(5000);
await waitForPaintToSettle(page);
await checkForScreenshot(
page,
+14 -1
View File
@@ -1,4 +1,11 @@
import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
import {
checkForScreenshot,
screenShotPaths,
test,
visitStudy,
waitForPaintToSettle,
waitForViewportsRendered,
} from './utils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.7695.4007.324475281161490036195179843543';
@@ -16,7 +23,13 @@ test('should hydrate SR reports correctly', async ({
await rightPanelPageObject.toggle();
await rightPanelPageObject.measurementsPanel.select();
await leftPanelPageObject.loadSeriesByModality('SR');
// The DICOMSRDisplayTool bails out when the viewport has no actors yet
// (see DICOMSRDisplayTool's hasActors guard), so we must wait until the
// underlying image has rendered an actor before screenshotting the SR
// overlay (line/rectangle).
await waitForViewportsRendered(page);
await page.waitForTimeout(2000);
await waitForPaintToSettle(page);
await checkForScreenshot(
page,
viewportPageObject.grid,
+22 -3
View File
@@ -1,5 +1,13 @@
import { Locator } from '@playwright/test';
import { checkForScreenshot, expect, screenShotPaths, test, visitStudy } from './utils';
import {
checkForScreenshot,
expect,
screenShotPaths,
test,
visitStudy,
waitForPaintToSettle, waitForViewportRenderCycle,
waitForViewportsRendered,
} from './utils';
async function expectNonEmptyDetailLines(lines: Locator) {
const lineCount = await lines.count();
@@ -27,7 +35,7 @@ test.beforeEach(async ({ page }) => {
}
});
});
test.describe.configure({ retries: 1 });
test('should hydrate SCOORD3D probe measurements correctly', async ({
page,
DOMOverlayPageObject,
@@ -90,8 +98,12 @@ test('should hydrate SCOORD3D probe measurements correctly', async ({
// Click the hydrate button to load the SCOORD3D probe measurements
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
// Wait for hydration to complete and rendering to stabilize
// Wait for hydration to complete and rendering to stabilize. SCOORD3D
// hydration can swap the displayed series (referenced vs. current volume),
// so we must wait for the new image set to render before screenshotting.
await waitForViewportsRendered(page);
await page.waitForTimeout(3000);
await waitForPaintToSettle(page);
// Take screenshot after hydration showing the probe measurements - use viewport locator
await checkForScreenshot(
@@ -129,9 +141,16 @@ test('should hydrate SCOORD3D probe measurements correctly', async ({
viewport.render();
}
});
await waitForViewportsRendered(page, { waitVolumeLoad: false });
// Click on a data row to jump to the measurement
const jumpRenderCycle = waitForViewportRenderCycle(page, {
renderedTimeout: 30000,
waitVolumeLoad: false,
});
await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
await jumpRenderCycle;
await waitForPaintToSettle(page);
// Take screenshot showing the jump to measurement functionality - use viewport locator
await checkForScreenshot(
+9 -4
View File
@@ -7,6 +7,8 @@ import {
screenShotPaths,
test,
visitStudy,
waitForPaintToSettle,
waitForViewportsRendered,
} from './utils';
async function expectNonEmptyDetailLines(lines: Locator) {
@@ -53,10 +55,13 @@ test('should hydrate SCOORD rectangle measurements correctly', async ({
// Double-click on the study browser thumbnail to load the SR
await leftPanelPageObject.loadSeriesByModality('SR');
await page.waitForTimeout(2000);
// Wait for the SR to load and stabilize before taking screenshot
await page.waitForTimeout(1000);
// The DICOMSRDisplayTool only draws the SCOORD rectangle once the
// underlying image has been rendered (it bails out when the viewport
// has no actors). Wait for the image to render before checking the
// overlay, otherwise the screenshot captures a blank canvas.
await waitForViewportsRendered(page);
await page.waitForTimeout(3000);
await waitForPaintToSettle(page);
const activeViewport = await viewportPageObject.active;
+2 -2
View File
@@ -42,11 +42,11 @@ test('should display the spline tool', async ({
const stats = splines[0].firstTargetStats!;
expect(stats.areaUnit).toBe('mm²');
expect(Math.round(stats.area as number)).toBe(38811);
expect(Math.round(stats.area as number)).toBe(38963);
const lines = activeViewport.getSvgAnnotationStatTextLines(splines[0].annotationUID);
await expect(lines).toHaveCount(1);
await expect(lines.nth(0)).toHaveText('Area: 38811 mm²');
await expect(lines.nth(0)).toHaveText('Area: 38963 mm²');
});
test('should restore viewport interactivity after deleting an in-progress Spline annotation via context menu', async ({
Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 997 B

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 KiB

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 KiB

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 503 KiB

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 549 KiB

After

Width:  |  Height:  |  Size: 546 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Loaded 100 of 172 files, more files were not shown because too many files have changed in this diff. Show more