test(e2e): update Playwright screenshots and add better tests and assertions for area calculations (#6022)
* Add (better) assertions for area calculation fixes. * Add SVG and measurement side panel area for freehand ROI test. * Add Playwright viewport screenshot scope migration guide. * Update cornerstonjs dependencies to 4.22.8. Ensure all versions of @babel/preset-env are 7.29.5. --------- Co-authored-by: Ghadeer Albattarni <165973963+GhadeerAlbattarni@users.noreply.github.com>
No files matched your search
@@ -15,7 +15,11 @@ test.beforeEach(async ({ page }) => {
|
||||
});
|
||||
|
||||
test.describe('3D four up Test', async () => {
|
||||
test('should render 3D four up correctly.', async ({ page, mainToolbarPageObject }) => {
|
||||
test('should render 3D four up correctly.', async ({
|
||||
page,
|
||||
mainToolbarPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await mainToolbarPageObject.layoutSelection.threeDFourUp.click();
|
||||
|
||||
await attemptAction(() => reduce3DViewportSize(page), 10, 100);
|
||||
@@ -24,7 +28,7 @@ test.describe('3D four up Test', async () => {
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.threeDFourUp.threeDFourUpDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
|
||||
@@ -15,10 +15,18 @@ test.beforeEach(async ({ page }) => {
|
||||
});
|
||||
|
||||
test.describe('3D main Test', async () => {
|
||||
test('should render 3D main correctly.', async ({ page, mainToolbarPageObject }) => {
|
||||
test('should render 3D main correctly.', async ({
|
||||
page,
|
||||
mainToolbarPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await mainToolbarPageObject.layoutSelection.threeDMain.click();
|
||||
await attemptAction(() => reduce3DViewportSize(page), 10, 100);
|
||||
await waitForViewportsRendered(page);
|
||||
await checkForScreenshot(page, page, screenShotPaths.threeDMain.threeDMainDisplayedCorrectly);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.threeDMain.threeDMainDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -15,14 +15,18 @@ test.beforeEach(async ({ page }) => {
|
||||
});
|
||||
|
||||
test.describe('3D only Test', async () => {
|
||||
test('should render 3D only correctly.', async ({ page, mainToolbarPageObject }) => {
|
||||
test('should render 3D only correctly.', async ({
|
||||
page,
|
||||
mainToolbarPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await mainToolbarPageObject.layoutSelection.threeDOnly.click();
|
||||
await attemptAction(() => reduce3DViewportSize(page), 10, 100);
|
||||
await waitForViewportsRendered(page);
|
||||
// Use a 4 percent diff pixel ratio to account for slight color differences in the 3D viewport
|
||||
await checkForScreenshot({
|
||||
page,
|
||||
locator: page,
|
||||
locator: viewportPageObject.grid,
|
||||
screenshotPath: screenShotPaths.threeDOnly.threeDOnlyDisplayedCorrectly,
|
||||
maxDiffPixelRatio: 0.04,
|
||||
});
|
||||
|
||||
@@ -15,14 +15,18 @@ test.beforeEach(async ({ page }) => {
|
||||
});
|
||||
|
||||
test.describe('3D primary Test', async () => {
|
||||
test('should render 3D primary correctly.', async ({ page, mainToolbarPageObject }) => {
|
||||
test('should render 3D primary correctly.', async ({
|
||||
page,
|
||||
mainToolbarPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await mainToolbarPageObject.layoutSelection.threeDPrimary.click();
|
||||
|
||||
await attemptAction(() => reduce3DViewportSize(page), 10, 100);
|
||||
await waitForViewportsRendered(page);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.threeDPrimary.threeDPrimaryDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
|
||||
@@ -20,5 +20,10 @@ test('should display the angle tool', async ({
|
||||
{ x: 550, y: 300 },
|
||||
]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.angle.angleDisplayedCorrectly);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.angle.angleDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -7,11 +7,16 @@ test.beforeEach(async ({ page }) => {
|
||||
});
|
||||
|
||||
test.describe('Axial Primary Test', async () => {
|
||||
test('should render Axial Primary correctly.', async ({ page, mainToolbarPageObject }) => {
|
||||
test('should render Axial Primary correctly.', async ({
|
||||
page,
|
||||
mainToolbarPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await mainToolbarPageObject.layoutSelection.axialPrimary.click();
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.axialPrimary.axialPrimaryDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
|
||||
@@ -19,9 +19,10 @@ test('should display the bidirectional tool', async ({
|
||||
{ x: 515, y: 339 },
|
||||
]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.bidirectional.bidirectionalDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -19,5 +19,9 @@ test('should display the circle tool', async ({
|
||||
{ x: 488, y: 247 },
|
||||
]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.circle.circleDisplayedCorrectly);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.circle.circleDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -21,5 +21,10 @@ test('should display the cobb angle tool', async ({
|
||||
{ x: 625, y: 291 },
|
||||
]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.cobbangle.cobbangleDisplayedCorrectly);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.cobbangle.cobbangleDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -21,13 +21,17 @@ test('should the context menu completely on screen and is not clipped for a poin
|
||||
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.contextMenu.preContextMenuNearBottomEdge);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.contextMenu.preContextMenuNearBottomEdge
|
||||
);
|
||||
|
||||
await activeViewport.normalizedClickAt([{ x: 0.55, y: 0.98 }], 'right');
|
||||
|
||||
await checkForScreenshot({
|
||||
page,
|
||||
locator: page,
|
||||
locator: viewportPageObject.grid,
|
||||
screenshotPath: screenShotPaths.contextMenu.contextMenuNearBottomEdgeNotClipped,
|
||||
});
|
||||
});
|
||||
@@ -19,5 +19,10 @@ test('should display the ellipse tool', async ({
|
||||
{ x: 508, y: 281 },
|
||||
]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.ellipse.ellipseDisplayedCorrectly);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.ellipse.ellipseDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -6,11 +6,16 @@ test.beforeEach(async ({ page }) => {
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
});
|
||||
|
||||
test('should flip the image horizontally', async ({ page, mainToolbarPageObject }) => {
|
||||
test('should flip the image horizontally', async ({
|
||||
page,
|
||||
mainToolbarPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await mainToolbarPageObject.moreTools.flipHorizontal.click();
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.flipHorizontal.flipHorizontalDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -1,4 +1,10 @@
|
||||
import { expect, test, visitStudy, subscribeToMeasurementAdded } from './utils';
|
||||
import {
|
||||
expect,
|
||||
getAnnotationStats,
|
||||
subscribeToMeasurementAdded,
|
||||
test,
|
||||
visitStudy,
|
||||
} from './utils';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const studyInstanceUID = '1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5';
|
||||
@@ -32,3 +38,70 @@ test('should not fire MEASUREMENT_ADDED when clicking the annotation text', asyn
|
||||
await measurementAdded.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
test('rectangle and freehand at identical coordinates should yield comparable area', async ({
|
||||
page,
|
||||
DOMOverlayPageObject,
|
||||
mainToolbarPageObject,
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
// Open measurements panel first — needed later for the delete action, and
|
||||
// opening up-front means both shapes are drawn against the same viewport
|
||||
// layout (so normalized coords don't shift between draws).
|
||||
await rightPanelPageObject.toggle();
|
||||
await rightPanelPageObject.measurementsPanel.select();
|
||||
|
||||
const activeViewport = await viewportPageObject.active;
|
||||
|
||||
// End on the left edge just shy of the start so cornerstone3D's proximity-
|
||||
// close triggers via the standard mouseup path (which fires
|
||||
// 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.
|
||||
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 },
|
||||
];
|
||||
const [topLeft, , bottomRight] = corners;
|
||||
|
||||
await mainToolbarPageObject.measurementTools.rectangleROI.click();
|
||||
await activeViewport.normalizedClickAt([topLeft, bottomRight]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
|
||||
const rectangles = await getAnnotationStats(page, { toolName: 'RectangleROI' });
|
||||
const rectArea = rectangles[0].firstTargetStats!.area as number;
|
||||
const rectRow = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0);
|
||||
const rectSvgLines = activeViewport.getSvgAnnotationStatTextLines(rectangles[0].annotationUID);
|
||||
await expect(rectSvgLines.nth(0)).toHaveText(`Area: ${Math.round(rectArea)} mm²`);
|
||||
await expect(rectRow.stats.primary.lines.nth(0)).toHaveText(`${Math.round(rectArea)} mm²`);
|
||||
|
||||
// Remove the rectangle so the freehand can drag through the same coords
|
||||
// without grabbing the rectangle's handles.
|
||||
await rectRow.actions.delete();
|
||||
|
||||
await mainToolbarPageObject.measurementTools.freehandROI.click();
|
||||
await activeViewport.normalizedPathDragAt({
|
||||
path: corners,
|
||||
config: { steps: 20, delay: 30 },
|
||||
});
|
||||
|
||||
const freehands = await getAnnotationStats(page, { toolName: 'PlanarFreehandROI' });
|
||||
const freehandArea = freehands[0].firstTargetStats!.area as number;
|
||||
const freehandRow = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0);
|
||||
const freehandSvgLines = activeViewport.getSvgAnnotationStatTextLines(freehands[0].annotationUID);
|
||||
await expect(freehandSvgLines.nth(0)).toHaveText(`Area: ${Math.round(freehandArea)} mm²`);
|
||||
await expect(freehandRow.stats.primary.lines.nth(0)).toHaveText(`${Math.round(freehandArea)} mm²`);
|
||||
|
||||
const pctDiff = (Math.abs(rectArea - freehandArea) / rectArea) * 100;
|
||||
console.log(
|
||||
`Rectangle: ${rectArea.toFixed(2)} mm² | ` +
|
||||
`Freehand: ${freehandArea.toFixed(2)} mm² | ` +
|
||||
`diff: ${pctDiff.toFixed(2)}%`
|
||||
);
|
||||
|
||||
expect(pctDiff).toBeLessThan(1);
|
||||
});
|
||||
@@ -12,8 +12,12 @@ test.beforeEach(async ({ page }) => {
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
});
|
||||
|
||||
test('should invert the image', async ({ page, mainToolbarPageObject }) => {
|
||||
test('should invert the image', async ({ page, mainToolbarPageObject, viewportPageObject }) => {
|
||||
await mainToolbarPageObject.moreTools.invert.click();
|
||||
await waitForViewportsRendered(page);
|
||||
await checkForScreenshot(page, page, screenShotPaths.invert.invertDisplayedCorrectly);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.invert.invertDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -54,7 +54,11 @@ test('should hydrate in MPR correctly', async ({
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
|
||||
// scroll away
|
||||
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.initialDraw);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.jumpToMeasurementMPR.initialDraw
|
||||
);
|
||||
|
||||
// Focus on the canvas first, then use mouse wheel to scroll away
|
||||
await page.evaluate(() => {
|
||||
@@ -78,11 +82,19 @@ test('should hydrate in MPR correctly', async ({
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.scrollAway);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.jumpToMeasurementMPR.scrollAway
|
||||
);
|
||||
|
||||
await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.jumpToMeasurementStack);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.jumpToMeasurementMPR.jumpToMeasurementStack
|
||||
);
|
||||
|
||||
await mainToolbarPageObject.layoutSelection.MPR.click();
|
||||
|
||||
@@ -93,19 +105,27 @@ test('should hydrate in MPR correctly', async ({
|
||||
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.jumpInMPR);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.jumpToMeasurementMPR.jumpInMPR
|
||||
);
|
||||
|
||||
await leftPanelPageObject.loadSeriesByDescription('Lung 3.0 CE');
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.jumpToMeasurementMPR.changeSeriesInMPR);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.jumpToMeasurementMPR.changeSeriesInMPR
|
||||
);
|
||||
|
||||
await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.jumpToMeasurementMPR.jumpToMeasurementAfterSeriesChange
|
||||
);
|
||||
});
|
||||
@@ -38,7 +38,11 @@ test('should prevent editing of label map segmentations when panelSegmentation.d
|
||||
// navigate to the 12th image and ensure the correct overlay is displayed
|
||||
await press({ page, key: 'ArrowDown', nTimes: 11 });
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.labelMapSegLocking.globalLockedSegPreEdit);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.labelMapSegLocking.globalLockedSegPreEdit
|
||||
);
|
||||
|
||||
// Attempt to erase the segmentations.
|
||||
await rightPanelPageObject.labelMapSegmentationPanel.tools.eraser.click();
|
||||
@@ -61,7 +65,11 @@ test('should prevent editing of label map segmentations when panelSegmentation.d
|
||||
end: { x: 1.0, y: 0.75 },
|
||||
});
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.labelMapSegLocking.globalLockedSegPostEdit);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.labelMapSegLocking.globalLockedSegPostEdit
|
||||
);
|
||||
});
|
||||
|
||||
test('should allow editing of label map segmentations when panelSegmentation.disableEditing is false', async ({
|
||||
@@ -89,7 +97,11 @@ test('should allow editing of label map segmentations when panelSegmentation.dis
|
||||
// navigate to the 12th image and ensure the correct overlay is displayed
|
||||
await press({ page, key: 'ArrowDown', nTimes: 11 });
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.labelMapSegLocking.globalUnlockedSegPreEdit);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.labelMapSegLocking.globalUnlockedSegPreEdit
|
||||
);
|
||||
|
||||
// Attempt to erase the segmentations.
|
||||
await rightPanelPageObject.labelMapSegmentationPanel.tools.eraser.click();
|
||||
@@ -114,7 +126,7 @@ test('should allow editing of label map segmentations when panelSegmentation.dis
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.labelMapSegLocking.globalUnlockedSegPostEdit
|
||||
);
|
||||
});
|
||||
@@ -19,5 +19,10 @@ test('should display the length tool', async ({
|
||||
{ x: 544, y: 232 },
|
||||
]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.length.lengthDisplayedCorrectly);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.length.lengthDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -1,4 +1,11 @@
|
||||
import { checkForScreenshot, screenShotPaths, test, visitStudy, expect } from './utils';
|
||||
import {
|
||||
checkForScreenshot,
|
||||
expect,
|
||||
getAnnotationStats,
|
||||
screenShotPaths,
|
||||
test,
|
||||
visitStudy,
|
||||
} from './utils';
|
||||
import { press } from './utils/keyboardUtils';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -24,7 +31,23 @@ test('should display the livewire tool', async ({
|
||||
{ x: 383, y: 461 },
|
||||
]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.livewire.livewireDisplayedCorrectly);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.livewire.livewireDisplayedCorrectly
|
||||
);
|
||||
|
||||
const livewires = await getAnnotationStats(page, { toolName: 'LivewireContour' });
|
||||
expect(livewires.length).toBeGreaterThan(0);
|
||||
|
||||
const stats = livewires[0].firstTargetStats!;
|
||||
expect(stats.areaUnit).toBe('mm²');
|
||||
expect(Math.round(stats.area as number)).toBe(28906);
|
||||
|
||||
const lines = activeViewport.getSvgAnnotationStatTextLines(livewires[0].annotationUID);
|
||||
await expect(lines).toHaveCount(1);
|
||||
await expect(lines.nth(0)).toHaveText('Area: 28906 mm²');
|
||||
});
|
||||
|
||||
test('should restore viewport interactivity after deleting an in-progress Livewire annotation via context menu', async ({
|
||||
|
||||
@@ -7,8 +7,17 @@ test.beforeEach(async ({ page }) => {
|
||||
});
|
||||
|
||||
test.describe('MPR Test', async () => {
|
||||
test('should render MPR correctly.', async ({ page, mainToolbarPageObject }) => {
|
||||
test('should render MPR correctly.', async ({
|
||||
page,
|
||||
mainToolbarPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await mainToolbarPageObject.layoutSelection.MPR.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.mpr.mprDisplayedCorrectly);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.mpr.mprDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -25,7 +25,7 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.mprThenRTOverlayNoHydration.mprPreRTOverlayNoHydration
|
||||
);
|
||||
|
||||
@@ -48,6 +48,7 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me
|
||||
|
||||
await checkForScreenshot({
|
||||
page,
|
||||
locator: viewportPageObject.grid,
|
||||
screenshotPath: screenShotPaths.mprThenRTOverlayNoHydration.mprPostRTOverlayNoHydration,
|
||||
normalizedClip: { x: 0, y: 0, width: 1.0, height: 0.75 }, // clip to avoid any popups concerning surface creation and clipping
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ test('should launch MPR with unhydrated SEG chosen from the data overlay menu',
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.mprThenSEGOverlayNoHydration.mprPreSEGOverlayNoHydration
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ test('should launch MPR with unhydrated SEG chosen from the data overlay menu',
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.mprThenSEGOverlayNoHydration.mprPostSEGOverlayNoHydration
|
||||
);
|
||||
});
|
||||
@@ -69,15 +69,15 @@ test('checks if measurement item can be relabeled under Measurements panel', asy
|
||||
|
||||
// Open measurement panel confirm default empty
|
||||
await rightPanelPageObject.measurementsPanel.select();
|
||||
const measurementRow = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).locator;
|
||||
await expect(measurementRow).toContainText('(empty)');
|
||||
const measurementRow = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0);
|
||||
await expect(measurementRow.title).toHaveText('(empty)');
|
||||
|
||||
// Expand and click rename
|
||||
await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).actions.rename(relabelText);
|
||||
|
||||
// Check dialog closed and renamed
|
||||
await expect(DOMOverlayPageObject.dialog.input.locator).toBeHidden();
|
||||
await expect(measurementRow).toContainText(relabelText);
|
||||
await expect(measurementRow.title).toHaveText(relabelText);
|
||||
});
|
||||
|
||||
test('checks if measurement item can be relabeled through the context menu on the viewport', async ({
|
||||
@@ -98,8 +98,8 @@ test('checks if measurement item can be relabeled through the context menu on th
|
||||
|
||||
// Open measurement panel confirm default empty
|
||||
await rightPanelPageObject.measurementsPanel.select();
|
||||
const measurementRow = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).locator;
|
||||
await expect(measurementRow).toContainText('(empty)');
|
||||
const measurementRow = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0);
|
||||
await expect(measurementRow.title).toHaveText('(empty)');
|
||||
|
||||
// Right click and click rename
|
||||
await page.waitForTimeout(200); // small delay for context menu
|
||||
@@ -117,7 +117,7 @@ test('checks if measurement item can be relabeled through the context menu on th
|
||||
|
||||
// Check dialog closed and renamed
|
||||
await expect(DOMOverlayPageObject.dialog.title).toBeHidden();
|
||||
await expect(measurementRow).toContainText(relabelText);
|
||||
await expect(measurementRow.title).toHaveText(relabelText);
|
||||
});
|
||||
|
||||
test('checks if image would jump when clicked on a measurement item', async ({
|
||||
|
||||
@@ -49,6 +49,7 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
|
||||
|
||||
await checkForScreenshot({
|
||||
page,
|
||||
locator: viewportPageObject.grid,
|
||||
screenshotPath: screenShotPaths.multipleSegmentationDataOverlays.threeSegOverlaysInOverlayMenu,
|
||||
});
|
||||
|
||||
@@ -58,6 +59,7 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
|
||||
|
||||
await checkForScreenshot({
|
||||
page,
|
||||
locator: viewportPageObject.grid,
|
||||
screenshotPath: screenShotPaths.multipleSegmentationDataOverlays.threeSegOverlaysInOverlayMenu,
|
||||
});
|
||||
|
||||
@@ -70,6 +72,7 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
|
||||
|
||||
await checkForScreenshot({
|
||||
page,
|
||||
locator: viewportPageObject.grid,
|
||||
screenshotPath: screenShotPaths.multipleSegmentationDataOverlays.overlaysDisplayed,
|
||||
});
|
||||
|
||||
@@ -85,6 +88,7 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
|
||||
|
||||
await checkForScreenshot({
|
||||
page,
|
||||
locator: viewportPageObject.grid,
|
||||
screenshotPath: screenShotPaths.multipleSegmentationDataOverlays.overlaySEGsAndRTDisplayed,
|
||||
});
|
||||
|
||||
@@ -94,6 +98,7 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
|
||||
|
||||
await checkForScreenshot({
|
||||
page,
|
||||
locator: viewportPageObject.grid,
|
||||
screenshotPath: screenShotPaths.multipleSegmentationDataOverlays.overlaySEGsAndRTDisplayed,
|
||||
});
|
||||
});
|
||||
@@ -16,5 +16,10 @@ test('should display the probe tool', async ({
|
||||
const activeViewport = await viewportPageObject.active;
|
||||
await activeViewport.clickAt([{ x: 550, y: 200 }]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.probe.probeDisplayedCorrectly);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.probe.probeDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -12,7 +12,8 @@ test('should overlay an unhydrated RTSTRUCT over a display set that the RTSTRUCT
|
||||
page,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
const dataOverlayPageObject = (await viewportPageObject.getById('default')).overlayMenu.dataOverlay;
|
||||
const dataOverlayPageObject = (await viewportPageObject.getById('default')).overlayMenu
|
||||
.dataOverlay;
|
||||
await dataOverlayPageObject.toggle();
|
||||
await dataOverlayPageObject.addSegmentation('Contours on PET');
|
||||
|
||||
@@ -26,7 +27,7 @@ test('should overlay an unhydrated RTSTRUCT over a display set that the RTSTRUCT
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtDataOverlayForUnreferencedDisplaySetNoHydration.overlayFirstImage
|
||||
);
|
||||
|
||||
@@ -37,7 +38,7 @@ test('should overlay an unhydrated RTSTRUCT over a display set that the RTSTRUCT
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtDataOverlayForUnreferencedDisplaySetNoHydration.overlayMiddleImage
|
||||
);
|
||||
});
|
||||
@@ -37,7 +37,7 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtDataOverlayNoHydrationThenMPR.rtDataOverlayNoHydrationPreMPR
|
||||
);
|
||||
|
||||
@@ -47,7 +47,7 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtDataOverlayNoHydrationThenMPR.rtDataOverlayNoHydrationPostMPR
|
||||
);
|
||||
|
||||
|
||||
@@ -11,17 +11,30 @@ test('should hydrate RT reports correctly', async ({
|
||||
DOMOverlayPageObject,
|
||||
leftPanelPageObject,
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await rightPanelPageObject.toggle();
|
||||
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
|
||||
await page.waitForTimeout(5000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPreHydration);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydration.rtPreHydration
|
||||
);
|
||||
|
||||
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
|
||||
await page.waitForTimeout(5000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPostHydration);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydration.rtPostHydration
|
||||
);
|
||||
|
||||
await rightPanelPageObject.labelMapSegmentationPanel.panel.segmentByText('Small Sphere').click();
|
||||
await page.waitForTimeout(5000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtJumpToStructure);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydration.rtJumpToStructure
|
||||
);
|
||||
});
|
||||
@@ -11,11 +11,16 @@ test('should hydrate RT reports correctly', async ({
|
||||
DOMOverlayPageObject,
|
||||
leftPanelPageObject,
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await rightPanelPageObject.toggle();
|
||||
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydration2.rtPreHydration);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydration2.rtPreHydration
|
||||
);
|
||||
// wait for 3 seconds
|
||||
await page.evaluate(() => {
|
||||
// Access cornerstone directly from the window object
|
||||
@@ -40,5 +45,9 @@ test('should hydrate RT reports correctly', async ({
|
||||
|
||||
// should preserve zoom and pan and scroll position after hydration
|
||||
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPostHydration);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydration.rtPostHydration
|
||||
);
|
||||
});
|
||||
@@ -12,6 +12,7 @@ test('should hydrate an RTSTRUCT from MPR', async ({
|
||||
leftPanelPageObject,
|
||||
mainToolbarPageObject,
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await rightPanelPageObject.toggle();
|
||||
|
||||
@@ -19,25 +20,37 @@ test('should hydrate an RTSTRUCT from MPR', async ({
|
||||
|
||||
await page.waitForTimeout(10000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydrationFromMPR.mprBeforeRT);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydrationFromMPR.mprBeforeRT
|
||||
);
|
||||
|
||||
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydrationFromMPR.mprAfterRT);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydrationFromMPR.mprAfterRT
|
||||
);
|
||||
|
||||
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydrationFromMPR.mprAfterRTHydrated);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydrationFromMPR.mprAfterRTHydrated
|
||||
);
|
||||
|
||||
await mainToolbarPageObject.layoutSelection.axialPrimary.click();
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydrationFromMPR.mprAfterRTHydratedAfterLayoutChange
|
||||
);
|
||||
});
|
||||
@@ -12,6 +12,7 @@ test('should hydrate an RTSTRUCT and then launch MPR', async ({
|
||||
leftPanelPageObject,
|
||||
mainToolbarPageObject,
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await rightPanelPageObject.toggle();
|
||||
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
|
||||
@@ -20,7 +21,11 @@ test('should hydrate an RTSTRUCT and then launch MPR', async ({
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydrationThenMPR.rtPostHydration);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydrationThenMPR.rtPostHydration
|
||||
);
|
||||
|
||||
await mainToolbarPageObject.layoutSelection.axialPrimary.click();
|
||||
|
||||
@@ -28,7 +33,7 @@ test('should hydrate an RTSTRUCT and then launch MPR', async ({
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtHydrationThenMPR.rtPostHydrationMPRAxialPrimary
|
||||
);
|
||||
});
|
||||
@@ -11,17 +11,26 @@ test('should launch MPR with unhydrated RTSTRUCT', async ({
|
||||
leftPanelPageObject,
|
||||
mainToolbarPageObject,
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await rightPanelPageObject.toggle();
|
||||
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtNoHydrationThenMPR.rtNoHydrationPreMPR);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtNoHydrationThenMPR.rtNoHydrationPreMPR
|
||||
);
|
||||
|
||||
await mainToolbarPageObject.layoutSelection.MPR.click();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtNoHydrationThenMPR.rtNoHydrationPostMPR);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rtNoHydrationThenMPR.rtNoHydrationPostMPR
|
||||
);
|
||||
});
|
||||
@@ -19,5 +19,10 @@ test('should display the rectangle tool', async ({
|
||||
{ x: 591, y: 217 },
|
||||
]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.rectangle.rectangleDisplayedCorrectly);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rectangle.rectangleDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -6,9 +6,18 @@ test.beforeEach(async ({ page }) => {
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
});
|
||||
|
||||
test('should reset the image to its original state', async ({ page, mainToolbarPageObject }) => {
|
||||
test('should reset the image to its original state', async ({
|
||||
page,
|
||||
mainToolbarPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await mainToolbarPageObject.moreTools.rotateRight.click();
|
||||
await mainToolbarPageObject.moreTools.invert.click();
|
||||
await mainToolbarPageObject.moreTools.reset.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.reset.resetDisplayedCorrectly);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.reset.resetDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -6,7 +6,16 @@ test.beforeEach(async ({ page }) => {
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
});
|
||||
|
||||
test('should rotate the image to the right', async ({ page, mainToolbarPageObject }) => {
|
||||
test('should rotate the image to the right', async ({
|
||||
page,
|
||||
mainToolbarPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await mainToolbarPageObject.moreTools.rotateRight.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.rotateRight.rotateRightDisplayedCorrectly);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.rotateRight.rotateRightDisplayedCorrectly
|
||||
);
|
||||
});
|
||||
@@ -14,7 +14,8 @@ test('should launch MPR with unhydrated SEG chosen from the data overlay menu',
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await rightPanelPageObject.toggle();
|
||||
const dataOverlayPageObject = (await viewportPageObject.getById('default')).overlayMenu.dataOverlay;
|
||||
const dataOverlayPageObject = (await viewportPageObject.getById('default')).overlayMenu
|
||||
.dataOverlay;
|
||||
await dataOverlayPageObject.toggle();
|
||||
await dataOverlayPageObject.addSegmentation('Segmentation');
|
||||
|
||||
@@ -28,7 +29,7 @@ test('should launch MPR with unhydrated SEG chosen from the data overlay menu',
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.segDataOverlayNoHydrationThenMPR.segDataOverlayNoHydrationPreMPR
|
||||
);
|
||||
|
||||
@@ -38,7 +39,7 @@ test('should launch MPR with unhydrated SEG chosen from the data overlay menu',
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.segDataOverlayNoHydrationThenMPR.segDataOverlayNoHydrationPostMPR
|
||||
);
|
||||
|
||||
|
||||
@@ -11,12 +11,17 @@ test('should hydrate SEG reports correctly', async ({
|
||||
DOMOverlayPageObject,
|
||||
leftPanelPageObject,
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await rightPanelPageObject.toggle();
|
||||
await leftPanelPageObject.loadSeriesByDescription('SEG');
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.segHydration.segPreHydration);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.segHydration.segPreHydration
|
||||
);
|
||||
|
||||
await page.evaluate(() => {
|
||||
// Access cornerstone directly from the window object
|
||||
@@ -40,5 +45,9 @@ test('should hydrate SEG reports correctly', async ({
|
||||
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.segHydration.segPostHydration);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.segHydration.segPostHydration
|
||||
);
|
||||
});
|
||||
@@ -35,9 +35,10 @@ test.describe('3D four up SEG hydration', async () => {
|
||||
screenShotPaths.segHydrationFrom3DFourUp.threeDFourUpBeforeSEG
|
||||
);
|
||||
|
||||
let viewportRenderCycle = waitForViewportRenderCycle(page);
|
||||
await leftPanelPageObject.loadSeriesByDescription('SEG');
|
||||
|
||||
await waitForViewportsRendered(page);
|
||||
await viewportRenderCycle;
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
@@ -49,7 +50,7 @@ test.describe('3D four up SEG hydration', async () => {
|
||||
|
||||
// High rendered timeout needed: layout has 4 viewports (3D volume + MPR planes + SEG overlays),
|
||||
// which can take significantly longer time to fully render
|
||||
const viewportRenderCycle = waitForViewportRenderCycle(page, { renderedTimeout: 180000 });
|
||||
viewportRenderCycle = waitForViewportRenderCycle(page, { renderedTimeout: 180000 });
|
||||
|
||||
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ test('should properly display MPR for MR', async ({
|
||||
leftPanelPageObject,
|
||||
mainToolbarPageObject,
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await rightPanelPageObject.toggle();
|
||||
await leftPanelPageObject.loadSeriesByDescription('SEG');
|
||||
@@ -19,14 +20,18 @@ test('should properly display MPR for MR', async ({
|
||||
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.segHydrationThenMPR.segPostHydration);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.segHydrationThenMPR.segPostHydration
|
||||
);
|
||||
|
||||
await mainToolbarPageObject.layoutSelection.axialPrimary.click();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.segHydrationThenMPR.segPostHydrationMPRAxialPrimary
|
||||
);
|
||||
});
|
||||
@@ -11,17 +11,26 @@ test('should launch MPR with unhydrated SEG', async ({
|
||||
leftPanelPageObject,
|
||||
mainToolbarPageObject,
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await rightPanelPageObject.toggle();
|
||||
await leftPanelPageObject.loadSeriesByDescription('SEG');
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.segNoHydrationThenMPR.segNoHydrationPreMPR);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.segNoHydrationThenMPR.segNoHydrationPreMPR
|
||||
);
|
||||
|
||||
await mainToolbarPageObject.layoutSelection.MPR.click();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.segNoHydrationThenMPR.segNoHydrationPostMPR);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.segNoHydrationThenMPR.segNoHydrationPostMPR
|
||||
);
|
||||
});
|
||||
@@ -11,12 +11,17 @@ test('should hydrate SR reports correctly', async ({
|
||||
DOMOverlayPageObject,
|
||||
leftPanelPageObject,
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
await rightPanelPageObject.toggle();
|
||||
await rightPanelPageObject.measurementsPanel.select();
|
||||
await leftPanelPageObject.loadSeriesByModality('SR');
|
||||
await page.waitForTimeout(2000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.srHydration.srPreHydration);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.srHydration.srPreHydration
|
||||
);
|
||||
|
||||
await page.evaluate(() => {
|
||||
// Access cornerstone directly from the window object
|
||||
@@ -39,7 +44,11 @@ test('should hydrate SR reports correctly', async ({
|
||||
|
||||
await DOMOverlayPageObject.viewport.segmentationHydration.yes.click();
|
||||
await page.waitForTimeout(2000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.srHydration.srPostHydration);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.srHydration.srPostHydration
|
||||
);
|
||||
|
||||
await page.evaluate(() => {
|
||||
// Access cornerstone directly from the window object
|
||||
@@ -62,5 +71,9 @@ test('should hydrate SR reports correctly', async ({
|
||||
|
||||
await rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0).click();
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.srHydration.srJumpToMeasurement);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.srHydration.srJumpToMeasurement
|
||||
);
|
||||
});
|
||||
@@ -1,5 +1,14 @@
|
||||
import { Locator } from '@playwright/test';
|
||||
import { checkForScreenshot, expect, screenShotPaths, test, visitStudy } from './utils';
|
||||
|
||||
async function expectNonEmptyDetailLines(lines: Locator) {
|
||||
const lineCount = await lines.count();
|
||||
expect(lineCount).toBeGreaterThan(0);
|
||||
for (let lineIndex = 0; lineIndex < lineCount; lineIndex++) {
|
||||
await expect(lines.nth(lineIndex)).not.toBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.7310.5101.860473186348887719777907797922';
|
||||
const mode = 'viewer';
|
||||
@@ -92,19 +101,13 @@ 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(await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount()).toBeGreaterThan(
|
||||
0
|
||||
);
|
||||
expect(await rightPanelPageObject.measurementsPanel.panel.rows).not.toHaveCount(0);
|
||||
const rowCount = await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount();
|
||||
|
||||
// Verify that the measurements are probe measurements (not other types)
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
const measurementText = await rightPanelPageObject.measurementsPanel.panel
|
||||
.nthMeasurement(i)
|
||||
.locator.textContent();
|
||||
// Probe measurements should be present, verify they're not other measurement types
|
||||
expect(measurementText).toBeTruthy();
|
||||
const measurement = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(i);
|
||||
await expect(measurement.title).not.toBeEmpty();
|
||||
await expectNonEmptyDetailLines(measurement.stats.primary.lines);
|
||||
}
|
||||
|
||||
// Test jumping to a specific measurement by scrolling and clicking
|
||||
@@ -199,10 +202,8 @@ test('should display SCOORD3D probe measurements correctly', async ({
|
||||
|
||||
// Verify that the measurements are probe measurements (not other types like rectangle)
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
const measurementText = await rightPanelPageObject.measurementsPanel.panel
|
||||
.nthMeasurement(i)
|
||||
.locator.textContent();
|
||||
// Probe measurements should be present, verify they're not other measurement types
|
||||
expect(measurementText).toBeTruthy();
|
||||
const measurement = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(i);
|
||||
await expect(measurement.title).not.toBeEmpty();
|
||||
await expectNonEmptyDetailLines(measurement.stats.primary.lines);
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,21 @@
|
||||
import { checkForScreenshot, expect, screenShotPaths, test, visitStudy } from './utils';
|
||||
import type { Locator } from '@playwright/test';
|
||||
|
||||
import {
|
||||
checkForScreenshot,
|
||||
expect,
|
||||
getAnnotationStats,
|
||||
screenShotPaths,
|
||||
test,
|
||||
visitStudy,
|
||||
} from './utils';
|
||||
|
||||
async function expectNonEmptyDetailLines(lines: Locator) {
|
||||
const lineCount = await lines.count();
|
||||
expect(lineCount).toBeGreaterThan(0);
|
||||
for (let lineIndex = 0; lineIndex < lineCount; lineIndex++) {
|
||||
await expect(lines.nth(lineIndex)).not.toBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const studyInstanceUID = '1.2.840.113654.2.55.242841386983064378162007136685545369722';
|
||||
@@ -96,13 +113,11 @@ test('should hydrate SCOORD rectangle measurements correctly', async ({
|
||||
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 measurementText = await rightPanelPageObject.measurementsPanel.panel
|
||||
.nthMeasurement(i)
|
||||
.locator.textContent();
|
||||
// Rectangle measurements should be present, verify they're not other measurement types
|
||||
expect(measurementText).toBeTruthy();
|
||||
const measurement = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(i);
|
||||
await expect(measurement.title).not.toBeEmpty();
|
||||
await expectNonEmptyDetailLines(measurement.stats.primary.lines);
|
||||
await expectNonEmptyDetailLines(measurement.stats.secondary.lines);
|
||||
}
|
||||
|
||||
// Test jumping to a specific measurement by scrolling and clicking
|
||||
@@ -195,12 +210,38 @@ test('should display SCOORD rectangle measurements correctly', async ({
|
||||
const rowCount = await rightPanelPageObject.measurementsPanel.panel.getMeasurementCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
|
||||
// Verify that the measurements are rectangle measurements (not other types like probe)
|
||||
const firstMeasurement = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(0);
|
||||
const expectedFirstPrimaryDetailLines = ['276 mm²', 'Max: 598 HU', 'Lung structure'];
|
||||
await expect(firstMeasurement.stats.primary.lines).toHaveCount(
|
||||
expectedFirstPrimaryDetailLines.length
|
||||
);
|
||||
for (let lineIndex = 0; lineIndex < expectedFirstPrimaryDetailLines.length; lineIndex++) {
|
||||
await expect(firstMeasurement.stats.primary.lines.nth(lineIndex)).toHaveText(
|
||||
expectedFirstPrimaryDetailLines[lineIndex]
|
||||
);
|
||||
}
|
||||
|
||||
// Assert against the source-of-truth cachedStats on the cornerstone annotation,
|
||||
// independent of UI label formatting. Values should match the displayed labels
|
||||
// after rounding ("276 mm²", "Max: 598 HU").
|
||||
const rectangles = await getAnnotationStats(page, { toolName: 'RectangleROI' });
|
||||
expect(rectangles.length).toBeGreaterThan(0);
|
||||
|
||||
const stats = rectangles[0].firstTargetStats!;
|
||||
expect(stats.areaUnit).toBe('mm²');
|
||||
expect(Math.round(stats.area as number)).toBe(276);
|
||||
expect(Math.round(stats.max as number)).toBe(598);
|
||||
expect(stats.Modality).toBe('CT');
|
||||
expect(stats.modalityUnit).toBe('HU');
|
||||
|
||||
const lines = activeViewport.getSvgAnnotationStatTextLines(rectangles[0].annotationUID);
|
||||
await expect(lines).toHaveCount(5);
|
||||
await expect(lines.nth(0)).toHaveText('Area: 276 mm²');
|
||||
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
const measurementText = await rightPanelPageObject.measurementsPanel.panel
|
||||
.nthMeasurement(i)
|
||||
.locator.textContent();
|
||||
// Rectangle measurements should be present, verify they're not other measurement types
|
||||
expect(measurementText).toBeTruthy();
|
||||
const measurement = rightPanelPageObject.measurementsPanel.panel.nthMeasurement(i);
|
||||
await expect(measurement.title).not.toBeEmpty();
|
||||
await expectNonEmptyDetailLines(measurement.stats.primary.lines);
|
||||
await expectNonEmptyDetailLines(measurement.stats.secondary.lines);
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,11 @@
|
||||
import { checkForScreenshot, screenShotPaths, test, visitStudy, expect } from './utils';
|
||||
import {
|
||||
checkForScreenshot,
|
||||
expect,
|
||||
getAnnotationStats,
|
||||
screenShotPaths,
|
||||
test,
|
||||
visitStudy,
|
||||
} from './utils';
|
||||
import { press } from './utils/keyboardUtils';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -24,7 +31,22 @@ test('should display the spline tool', async ({
|
||||
{ x: 383, y: 461 },
|
||||
]);
|
||||
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.spline.splineDisplayedCorrectly);
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
viewportPageObject.grid,
|
||||
screenShotPaths.spline.splineDisplayedCorrectly
|
||||
);
|
||||
|
||||
const splines = await getAnnotationStats(page, { toolName: 'SplineROI' });
|
||||
expect(splines.length).toBeGreaterThan(0);
|
||||
|
||||
const stats = splines[0].firstTargetStats!;
|
||||
expect(stats.areaUnit).toBe('mm²');
|
||||
expect(Math.round(stats.area as number)).toBe(38811);
|
||||
|
||||
const lines = activeViewport.getSvgAnnotationStatTextLines(splines[0].annotationUID);
|
||||
await expect(lines).toHaveCount(1);
|
||||
await expect(lines.nth(0)).toHaveText('Area: 38811 mm²');
|
||||
});
|
||||
|
||||
test('should restore viewport interactivity after deleting an in-progress Spline annotation via context menu', async ({
|
||||
|
||||
@@ -112,30 +112,58 @@ export class RightPanelPageObject {
|
||||
|
||||
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');
|
||||
const trackedMeasurementsPanel = page.getByTestId('trackedMeasurements-panel').last();
|
||||
const measurementTableRows = trackedMeasurementsPanel.locator(
|
||||
'[data-cy^="measurement-table-row-"]'
|
||||
);
|
||||
const getMeasurementStatsAt = (index: number) => {
|
||||
const dataRow = trackedMeasurementsPanel
|
||||
.getByTestId(`measurement-table-row-${index}`)
|
||||
.getByTestId('data-row');
|
||||
const locator = dataRow.locator('..').getByTestId('data-row-details');
|
||||
const primaryLocator = locator.getByTestId('data-row-details-primary');
|
||||
const secondaryLocator = locator.getByTestId('data-row-details-secondary');
|
||||
return {
|
||||
locator,
|
||||
primary: {
|
||||
locator: primaryLocator,
|
||||
lines: primaryLocator.getByTestId('data-row-detail-line'),
|
||||
},
|
||||
secondary: {
|
||||
locator: secondaryLocator,
|
||||
lines: secondaryLocator.getByTestId('data-row-detail-line'),
|
||||
},
|
||||
};
|
||||
};
|
||||
const getMeasurementDataRowAt = (index: number) => {
|
||||
const rowWrapper = trackedMeasurementsPanel.getByTestId(`measurement-table-row-${index}`);
|
||||
const dataRow = rowWrapper.getByTestId('data-row');
|
||||
const row = this.getPanelRowDataObject(rowWrapper);
|
||||
// Selection highlight (bg-popover) is on the inner DataRow, not the measurement-table-row wrapper
|
||||
return {
|
||||
...row,
|
||||
locator: dataRow,
|
||||
stats: getMeasurementStatsAt(index),
|
||||
};
|
||||
};
|
||||
const trackedMeasurementsMenu = 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();
|
||||
return await measurementTableRows.count();
|
||||
},
|
||||
rows: page.getByTestId('data-row'),
|
||||
locator: page.getByTestId('trackedMeasurements-panel').last(),
|
||||
rows: measurementTableRows,
|
||||
locator: trackedMeasurementsPanel,
|
||||
nthMeasurement(index: number) {
|
||||
return getMeasurementByIdx(index);
|
||||
},
|
||||
measurementByText(text: string) {
|
||||
return getMeasurementByText(text);
|
||||
return getMeasurementDataRowAt(index);
|
||||
},
|
||||
},
|
||||
select: async () => {
|
||||
await menuButton.click();
|
||||
await trackedMeasurementsMenu.click();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -414,7 +442,6 @@ export class RightPanelPageObject {
|
||||
get microscopyPanel() {
|
||||
const page = this.page;
|
||||
const getMeasurementByIdx = (index: number) => this.getPanelRowByIdx(index);
|
||||
const getMeasurementByText = (text: string) => this.getPanelRowByText(text);
|
||||
|
||||
return {
|
||||
locator: page.getByTestId('measurements-panel'),
|
||||
@@ -424,9 +451,6 @@ export class RightPanelPageObject {
|
||||
nthMeasurement(index: number) {
|
||||
return getMeasurementByIdx(index);
|
||||
},
|
||||
measurementByText(text: string) {
|
||||
return getMeasurementByText(text);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,13 @@ import {
|
||||
simulateDoubleClickOnElement,
|
||||
simulateNormalizedClicksOnElement,
|
||||
simulateNormalizedDragOnElement,
|
||||
simulateNormalizedPathDragOnElement,
|
||||
} from '../utils';
|
||||
import { DataOverlayPageObject } from './DataOverlayPageObject';
|
||||
import { DOMOverlayPageObject } from './DOMOverlayPageObject';
|
||||
import { MagnifyGlassPageObject } from './MagnifyGlassPageObject';
|
||||
|
||||
type SvgInnerElement = 'circle' | 'path' | 'd' | 'line';
|
||||
type SvgInnerElement = 'circle' | 'path' | 'd' | 'line' | 'g';
|
||||
|
||||
type NormalizedDragParams = {
|
||||
start: { x: number; y: number };
|
||||
@@ -18,6 +19,11 @@ type NormalizedDragParams = {
|
||||
config?: { button?: 'left' | 'right' | 'middle'; delay?: number; steps?: number };
|
||||
};
|
||||
|
||||
type NormalizedPathDragParams = {
|
||||
path: { x: number; y: number }[];
|
||||
config?: { button?: 'left' | 'right' | 'middle'; delay?: number; steps?: number };
|
||||
};
|
||||
|
||||
export interface IOverlayText {
|
||||
get windowLevel(): Locator;
|
||||
get instanceNumber(): Locator;
|
||||
@@ -56,6 +62,7 @@ export interface IViewportPageObject {
|
||||
button?: 'left' | 'right' | 'middle'
|
||||
) => Promise<void>;
|
||||
normalizedDragAt: (params: NormalizedDragParams) => Promise<void>;
|
||||
normalizedPathDragAt: (params: NormalizedPathDragParams) => Promise<void>;
|
||||
orientationMarkers: {
|
||||
topMid: Locator;
|
||||
leftMid: Locator;
|
||||
@@ -81,6 +88,7 @@ export interface IViewportPageObject {
|
||||
};
|
||||
pane: Locator;
|
||||
svg: (innerElement?: SvgInnerElement) => Locator;
|
||||
getSvgAnnotationStatTextLines: (uid: string) => Locator;
|
||||
navigationArrows: {
|
||||
locator: Locator;
|
||||
prev: {
|
||||
@@ -297,6 +305,15 @@ export class ViewportPageObject {
|
||||
steps: params.config?.steps,
|
||||
});
|
||||
},
|
||||
normalizedPathDragAt: async (params: NormalizedPathDragParams) => {
|
||||
await simulateNormalizedPathDragOnElement({
|
||||
locator: viewport,
|
||||
path: params.path,
|
||||
button: params.config?.button,
|
||||
delay: params.config?.delay,
|
||||
steps: params.config?.steps,
|
||||
});
|
||||
},
|
||||
orientationMarkers: this.getOrientationMarkers(viewport),
|
||||
overlayText: this.getOverlayText(viewport),
|
||||
overlayMenu: await this.getOverlayMenu(viewport),
|
||||
@@ -304,6 +321,11 @@ export class ViewportPageObject {
|
||||
svg: (innerElement?: SvgInnerElement) => {
|
||||
return this.getSvg(viewport, innerElement);
|
||||
},
|
||||
getSvgAnnotationStatTextLines: (uid: string) => {
|
||||
return this.getSvg(viewport)
|
||||
.locator(`g[data-annotation-uid="${uid}"]`)
|
||||
.locator('tspan');
|
||||
},
|
||||
navigationArrows: this.getNavigationArrows(viewport),
|
||||
sliceNavigation: this.getSliceNavigation(viewport),
|
||||
magnifyGlass: new MagnifyGlassPageObject(this.page, viewport),
|
||||
|
||||
|
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 282 KiB |
|
Before Width: | Height: | Size: 304 KiB After Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 349 KiB After Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 257 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 271 KiB |
|
Before Width: | Height: | Size: 267 KiB After Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 259 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 267 KiB After Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 257 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 265 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 303 KiB After Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 296 KiB After Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 257 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 34 KiB |