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>
This commit is contained in:
1 parent
cb0cbe702c
commit
fe16e80cf3
174 files changed
+1071
-751
No files matched your search
@@ -0,0 +1,64 @@
|
||||
type TargetStats = Record<string, unknown>;
|
||||
|
||||
type AnnotationStats = {
|
||||
annotationUID: string;
|
||||
toolName: string;
|
||||
referencedImageId?: string;
|
||||
FrameOfReferenceUID?: string;
|
||||
cachedStats: Record<string, TargetStats>;
|
||||
targetIds: string[];
|
||||
firstTargetStats: TargetStats | undefined;
|
||||
};
|
||||
|
||||
type Options = {
|
||||
toolName?: string | string[];
|
||||
attempts?: number;
|
||||
intervalMs?: number;
|
||||
};
|
||||
|
||||
const getAnnotationStats = async (page, options: Options = {}): Promise<AnnotationStats[]> => {
|
||||
const { toolName, attempts = 20, intervalMs = 500 } = options;
|
||||
|
||||
const toolNames =
|
||||
toolName === undefined ? undefined : Array.isArray(toolName) ? toolName : [toolName];
|
||||
|
||||
for (let i = 0; i < attempts; i++) {
|
||||
const results: AnnotationStats[] = await page.evaluate(
|
||||
({ toolNames }) => {
|
||||
const cornerstoneTools = (window as any).cornerstoneTools;
|
||||
const all = cornerstoneTools?.annotation?.state?.getAllAnnotations?.() ?? [];
|
||||
|
||||
return all
|
||||
.filter(a => !toolNames || toolNames.includes(a?.metadata?.toolName))
|
||||
.map(a => {
|
||||
const cachedStats = a?.data?.cachedStats ?? {};
|
||||
const targetIds = Object.keys(cachedStats);
|
||||
return {
|
||||
annotationUID: a.annotationUID,
|
||||
toolName: a.metadata?.toolName,
|
||||
referencedImageId: a.metadata?.referencedImageId,
|
||||
FrameOfReferenceUID: a.metadata?.FrameOfReferenceUID,
|
||||
cachedStats,
|
||||
targetIds,
|
||||
firstTargetStats: targetIds.length ? cachedStats[targetIds[0]] : undefined,
|
||||
};
|
||||
});
|
||||
},
|
||||
{ toolNames }
|
||||
);
|
||||
|
||||
const populated = results.filter(r => r.targetIds.length > 0);
|
||||
if (populated.length > 0) {
|
||||
return populated;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`No annotations${toolNames ? ` for tool(s) ${toolNames.join(',')}` : ''} with populated cachedStats found`
|
||||
);
|
||||
};
|
||||
|
||||
export { getAnnotationStats };
|
||||
export type { AnnotationStats, TargetStats };
|
||||
@@ -11,11 +11,15 @@ import {
|
||||
simulateNormalizedClickOnElement,
|
||||
simulateNormalizedClicksOnElement,
|
||||
} from './simulateClicksOnElement';
|
||||
import { simulateNormalizedDragOnElement } from './simulateDragOnElement';
|
||||
import {
|
||||
simulateNormalizedDragOnElement,
|
||||
simulateNormalizedPathDragOnElement,
|
||||
} from './simulateDragOnElement';
|
||||
import { reduce3DViewportSize } from './reduce3DviewportSize';
|
||||
import { getMousePosition, initializeMousePositionTracker } from './mouseUtils';
|
||||
import { getSUV } from './getSUV';
|
||||
import { getTMTVModalityUnit } from './getTMTVModalityUnit';
|
||||
import { getAnnotationStats } from './getAnnotationStats';
|
||||
import { clearAllAnnotations } from './clearAllAnnotations';
|
||||
import { scrollVolumeViewport } from './scrollVolumeViewport';
|
||||
import { attemptAction } from './attemptAction';
|
||||
@@ -41,11 +45,13 @@ export {
|
||||
simulateNormalizedClickOnElement,
|
||||
simulateNormalizedClicksOnElement,
|
||||
simulateNormalizedDragOnElement,
|
||||
simulateNormalizedPathDragOnElement,
|
||||
reduce3DViewportSize,
|
||||
getMousePosition,
|
||||
initializeMousePositionTracker,
|
||||
getSUV,
|
||||
getTMTVModalityUnit,
|
||||
getAnnotationStats,
|
||||
clearAllAnnotations,
|
||||
scrollVolumeViewport,
|
||||
attemptAction,
|
||||
|
||||
@@ -186,13 +186,6 @@ const screenShotPaths = {
|
||||
secondLoadPostHydration: 'secondLoadPostHydration.png',
|
||||
viewportAfterSecondDelete: 'viewportAfterSecondDelete.png',
|
||||
},
|
||||
crosshairs: {
|
||||
crosshairsRendered: 'crosshairsRendered.png',
|
||||
crosshairsRotated: 'crosshairsRotated.png',
|
||||
crosshairsSlabThickness: 'crosshairsSlabThickness.png',
|
||||
crosshairsResetToolbar: 'crosshairsResetToolbar.png',
|
||||
crosshairsNewDisplayset: 'crosshairsNewDisplayset.png',
|
||||
},
|
||||
tmtvRendering: {
|
||||
tmtvDisplayedCorrectly: 'tmtvDisplayedCorrectly.png',
|
||||
},
|
||||
|
||||
@@ -1,14 +1,80 @@
|
||||
import { Locator } from 'playwright';
|
||||
|
||||
/**
|
||||
* Simulates a drag operation on an element using normalized coordinates.
|
||||
* Simulates a continuous drag along a multi-point path on an element, using
|
||||
* coordinates normalized to the element's bounding box.
|
||||
*
|
||||
* mousedown fires at path[0]; the cursor smooth-moves through every subsequent
|
||||
* point in one stroke; mouseup fires at path[last] (unless mouseUp is false).
|
||||
*
|
||||
* @param locator - The locator to drag on.
|
||||
* @param start - The start point with x and y coordinates, normalized to the element's bounding box.
|
||||
* @param end - The end point with x and y coordinates, normalized to the element's bounding box.
|
||||
* @param button - The mouse button to use for the drag (default is 'left').
|
||||
* @param delay - Milliseconds between steps (default is 50).
|
||||
* @param steps - Number of intermediate positions for smooth dragging (default is 5).
|
||||
* @param path - Points (normalized to the element's bbox) describing the stroke.
|
||||
* @param button - The mouse button to use for the drag (default 'left').
|
||||
* @param delay - Milliseconds between intermediate moves (default 50).
|
||||
* @param steps - Number of intermediate positions per segment (default 10).
|
||||
* @param mouseUp - Whether to release the button at path[last] (default true).
|
||||
*/
|
||||
export async function simulateNormalizedPathDragOnElement({
|
||||
locator,
|
||||
path,
|
||||
button = 'left',
|
||||
delay = 50,
|
||||
steps = 10,
|
||||
mouseUp = true,
|
||||
}: {
|
||||
locator: Locator;
|
||||
path: { x: number; y: number }[];
|
||||
button?: 'left' | 'right' | 'middle';
|
||||
delay?: number;
|
||||
steps?: number;
|
||||
mouseUp?: boolean;
|
||||
}) {
|
||||
if (path.length < 2) {
|
||||
throw new Error('Path must contain at least 2 points');
|
||||
}
|
||||
|
||||
const bBox = await locator.boundingBox();
|
||||
if (!bBox) {
|
||||
throw new Error('Element bounding box not found');
|
||||
}
|
||||
|
||||
const page = locator.page();
|
||||
const absPath = path.map(p => ({
|
||||
x: bBox.x + p.x * bBox.width,
|
||||
y: bBox.y + p.y * bBox.height,
|
||||
}));
|
||||
|
||||
await page.mouse.move(absPath[0].x, absPath[0].y);
|
||||
await page.mouse.down({ button });
|
||||
|
||||
for (let segIdx = 1; segIdx < absPath.length; segIdx++) {
|
||||
const from = absPath[segIdx - 1];
|
||||
const to = absPath[segIdx];
|
||||
const deltaX = (to.x - from.x) / steps;
|
||||
const deltaY = (to.y - from.y) / steps;
|
||||
for (let i = 1; i <= steps; i++) {
|
||||
if (delay > 0) {
|
||||
await page.waitForTimeout(delay);
|
||||
}
|
||||
await page.mouse.move(from.x + deltaX * i, from.y + deltaY * i);
|
||||
}
|
||||
}
|
||||
|
||||
if (mouseUp) {
|
||||
await page.mouse.up({ button });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates a drag operation on an element using normalized coordinates.
|
||||
* Thin wrapper over simulateNormalizedPathDragOnElement with a 2-point path.
|
||||
*
|
||||
* @param locator - The locator to drag on.
|
||||
* @param start - The start point, normalized to the element's bounding box.
|
||||
* @param end - The end point, normalized to the element's bounding box.
|
||||
* @param button - The mouse button to use for the drag (default 'left').
|
||||
* @param delay - Milliseconds between steps (default 50).
|
||||
* @param steps - Number of intermediate positions for smooth dragging (default 10).
|
||||
*/
|
||||
export async function simulateNormalizedDragOnElement({
|
||||
locator,
|
||||
@@ -27,45 +93,12 @@ export async function simulateNormalizedDragOnElement({
|
||||
steps?: number;
|
||||
mouseUp?: boolean;
|
||||
}) {
|
||||
const bBox = await locator.boundingBox();
|
||||
if (!bBox) {
|
||||
throw new Error('Element bounding box not found');
|
||||
}
|
||||
|
||||
const startPosition = {
|
||||
x: bBox.x + start.x * bBox.width,
|
||||
y: bBox.y + start.y * bBox.height,
|
||||
};
|
||||
const endPosition = {
|
||||
x: bBox.x + end.x * bBox.width,
|
||||
y: bBox.y + end.y * bBox.height,
|
||||
};
|
||||
|
||||
// Get the page from the locator context
|
||||
const page = locator.page();
|
||||
|
||||
await page.mouse.move(startPosition.x, startPosition.y);
|
||||
|
||||
// Start drag
|
||||
await page.mouse.down({ button });
|
||||
|
||||
// Calculate intermediate positions for smooth dragging
|
||||
const deltaX = (endPosition.x - startPosition.x) / steps;
|
||||
const deltaY = (endPosition.y - startPosition.y) / steps;
|
||||
|
||||
// Move through intermediate positions
|
||||
for (let i = 1; i <= steps; i++) {
|
||||
const currentX = startPosition.x + deltaX * i;
|
||||
const currentY = startPosition.y + deltaY * i;
|
||||
// Add delay if specified
|
||||
if (delay > 0) {
|
||||
await page.waitForTimeout(delay);
|
||||
}
|
||||
await page.mouse.move(currentX, currentY);
|
||||
}
|
||||
|
||||
// End drag
|
||||
if (mouseUp) {
|
||||
await page.mouse.up({ button });
|
||||
}
|
||||
await simulateNormalizedPathDragOnElement({
|
||||
locator,
|
||||
path: [start, end],
|
||||
button,
|
||||
delay,
|
||||
steps,
|
||||
mouseUp,
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user