Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions tests/pages/ViewportPageObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ export interface IViewportPageObject {
scrollBy: (delta: number) => Promise<void>;
};
magnifyGlass: MagnifyGlassPageObject;
hideDemographicOverlayText: () => Promise<void>;
hideViewportOverlayText: () => Promise<void>;
hideAnnotationText: () => Promise<void>;
hideLateralityText: () => Promise<void>;
hideOrientationMarkerText: () => Promise<void>;
hideAllText: () => Promise<void>;
}

export class ViewportPageObject {
Expand Down Expand Up @@ -168,6 +174,63 @@ export class ViewportPageObject {
};
}

/**
* Hides matching elements from view by applying Tailwind's `hidden` class.
* No-op when the locator resolves to zero elements.
*/
private async hideLocatorElements(locator: Locator): Promise<void> {
const count = await locator.count();
if (count === 0) {
return;
}

await locator.evaluateAll(elements => {
elements.forEach(element => element.classList.add('hidden'));
});
}

private getHideTextMethods(viewport: Locator) {
const viewportOverlaySelector = '[data-cy^="viewport-overlay-"]';

const demographicOverlaySelector = [
`${viewportOverlaySelector} .overlay-item[title="Study date"]`,
`${viewportOverlaySelector} .overlay-item[title="Series description"]`,
].join(', ');

const annotationTextSelector = 'g[data-annotation-uid] text, g[data-annotation-uid] tspan';

const hideTextMethods = {
hideDemographicOverlayText: async () => {
await this.hideLocatorElements(viewport.locator(demographicOverlaySelector));
},

hideViewportOverlayText: async () => {
await this.hideLocatorElements(viewport.locator(viewportOverlaySelector));
},
hideAnnotationText: async () => {
await this.hideLocatorElements(viewport.locator(annotationTextSelector));
},
// Laterality is not part of the default OHIF overlay customization.
// This is a no-op unless the active mode explicitly adds an overlay item
// with title="Laterality" via viewportOverlay customization.
hideLateralityText: async () => {
await this.hideLocatorElements(
viewport.locator(`${viewportOverlaySelector} .overlay-item[title="Laterality"]`)
);
},
hideOrientationMarkerText: async () => {
await this.hideLocatorElements(viewport.locator('.orientation-marker'));
},
hideAllText: async () => {
await hideTextMethods.hideViewportOverlayText();
await hideTextMethods.hideAnnotationText();
await hideTextMethods.hideOrientationMarkerText();
},
};

return hideTextMethods;
}

private async getOverlayMenu(viewport: Locator) {
return {
dataOverlay: new DataOverlayPageObject(this.page, await this.getViewportId(viewport)),
Expand Down Expand Up @@ -328,6 +391,7 @@ export class ViewportPageObject {
navigationArrows: this.getNavigationArrows(viewport),
sliceNavigation: this.getSliceNavigation(viewport),
magnifyGlass: new MagnifyGlassPageObject(this.page, viewport),
...this.getHideTextMethods(viewport),
};
}

Expand Down
6 changes: 5 additions & 1 deletion tests/utils/checkForScreenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Locator, Page } from 'playwright';
import { promises as fs } from 'fs';
import path from 'path';

type CheckForScreenshotProps = {
export type CheckForScreenshotProps = {
page: Page;
locator?: Locator | Page;
screenshotPath: string;
Expand All @@ -18,6 +18,8 @@ type CheckForScreenshotProps = {
height: number;
};
fullPage?: boolean;
/** Runs at the start of each screenshot attempt (including the first), before capture. */
beforeAttempt?: () => void | Promise<void>;
};

const _isIntermediateScreenshotArtifact = (filename: string, screenshotPath: string) => {
Expand Down Expand Up @@ -87,6 +89,7 @@ const _checkForScreenshot = async (props: CheckForScreenshotProps) => {
threshold = 0.05,
normalizedClip,
fullPage = false,
beforeAttempt,
} = props;

let { locator = page } = props;
Expand All @@ -96,6 +99,7 @@ const _checkForScreenshot = async (props: CheckForScreenshotProps) => {

for (let i = 0; i < attempts; i++) {
try {
await beforeAttempt?.();
let clip;
if (normalizedClip) {
let boundingBox;
Expand Down
90 changes: 90 additions & 0 deletions tests/utils/checkForViewportScreenshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { IViewportPageObject } from '../pages/ViewportPageObject';
import { checkForScreenshot, type CheckForScreenshotProps } from './checkForScreenshot';

/**
* The category of text to hide before taking a viewport screenshot.
*
* - `'all'` — hides overlay, annotation, and orientation marker text.
* - `'overlay'` — hides all four viewport overlay corners. (all text in those corners).
* - `'demographic'` — hides Study date and Series description overlay items.
* - `'annotation'` — hides SVG annotation text boxes.
* - `'laterality'` — hides the Laterality overlay item (no-op in default OHIF config).
* - `'orientationMarker'`— hides orientation marker labels (e.g. A/P/L/R/H/F).
*/
export type HideTextOption =
| 'all'
| 'overlay'
| 'demographic'
| 'annotation'
| 'laterality'
| 'orientationMarker';

export type HideTextInput = HideTextOption | HideTextOption[];

type CheckForViewportScreenshotProps = Omit<CheckForScreenshotProps, 'beforeAttempt'> & {
viewport: IViewportPageObject;
/** Which category of text to hide before capturing.
* Defaults to `'all'`. Use checkForScreenshot directly when no text should be hidden.
*
* Pass an array to combine categories (e.g. `['overlay', 'orientationMarker']`).
*/
hideText?: HideTextInput;
};

const hideTextHandlers: Record<HideTextOption, (vp: IViewportPageObject) => Promise<void>> = {
all: vp => vp.hideAllText(),
overlay: vp => vp.hideViewportOverlayText(),
demographic: vp => vp.hideDemographicOverlayText(),
annotation: vp => vp.hideAnnotationText(),
laterality: vp => vp.hideLateralityText(),
orientationMarker: vp => vp.hideOrientationMarkerText(),
};

async function applyHideText(
viewport: IViewportPageObject,
hideText: HideTextInput
): Promise<void> {
const options = Array.isArray(hideText) ? hideText : [hideText];
if (options.length === 0) {
return;
}
if (options.includes('all')) {
await viewport.hideAllText();
return;
}
for (const option of options) {
await hideTextHandlers[option](viewport);
}
}

/**
* Viewport-scoped screenshot comparison that hides text before capture.
* Defaults to hiding all text ('all').
* Delegates to `checkForScreenshot` for retry logic and pixel comparison.
*
* Use `checkForScreenshot` when nothing should be hidden.
*
* @example
* await checkForViewportScreenshot({
* page,
* viewport: activeViewport,
* screenshotPath: screenShotPaths.length.lengthDisplayedCorrectly,
* hideText: 'overlay',
* });
*/
export const checkForViewportScreenshot = async ({
page,
viewport,
screenshotPath,
hideText = 'all',
locator,
...rest
}: CheckForViewportScreenshotProps): Promise<boolean> => {
return checkForScreenshot({
page,
locator: locator ?? viewport.pane,
screenshotPath,
...rest,
beforeAttempt: () => applyHideText(viewport, hideText),
});
};
7 changes: 3 additions & 4 deletions tests/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { visitStudy, visitStudyOptions } from './visitStudy';
import {
addOHIFConfiguration,
addOHIFGlobalCustomizations,
} from './OHIFConfiguration';
import { addOHIFConfiguration, addOHIFGlobalCustomizations } from './OHIFConfiguration';
import { checkForScreenshot } from './checkForScreenshot';
import { checkForViewportScreenshot } from './checkForViewportScreenshot';
import { screenShotPaths } from './screenShotPaths';
import {
simulateClicksOnElement,
Expand Down Expand Up @@ -44,6 +42,7 @@ export {
addOHIFConfiguration,
addOHIFGlobalCustomizations,
checkForScreenshot,
checkForViewportScreenshot,
screenShotPaths,
simulateClicksOnElement,
simulateDoubleClickOnElement,
Expand Down
Loading