164 lines
4.5 KiB
TypeScript
164 lines
4.5 KiB
TypeScript
import type { Page } from '@playwright/test';
|
|
|
|
type WaitForAnyViewportNeedsRenderOptions = {
|
|
timeout?: number;
|
|
};
|
|
|
|
type WaitForViewportsRenderedOptions = {
|
|
timeout?: number;
|
|
/**
|
|
* If true (default), also waits for any volume actors referenced by the
|
|
* viewports to report loaded.
|
|
*/
|
|
waitVolumeLoad?: boolean;
|
|
};
|
|
|
|
type WaitForRenderCycleToCompleteOptions = {
|
|
/**
|
|
* Timeout for waiting until at least one viewport reaches `needsRender`.
|
|
*/
|
|
needsRenderTimeout?: number;
|
|
/**
|
|
* Timeout for waiting until all viewports are `rendered`
|
|
* (and optionally volume-loaded).
|
|
*/
|
|
renderedTimeout?: number;
|
|
/**
|
|
* If true (default), also waits for any volume actors referenced by the
|
|
* viewports to report loaded during the rendered phase.
|
|
*/
|
|
waitVolumeLoad?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Waits for a full render cycle:
|
|
* 1) any viewport requests render (`needsRender`)
|
|
* 2) all viewports finish rendering (`rendered`)
|
|
*/
|
|
const waitForViewportRenderCycle = async (
|
|
page: Page,
|
|
options: WaitForRenderCycleToCompleteOptions = {}
|
|
) => {
|
|
const { needsRenderTimeout = 5000, renderedTimeout = 15000, waitVolumeLoad = true } = options;
|
|
await waitForAnyViewportNeedsRender(page, { timeout: needsRenderTimeout });
|
|
await waitForViewportsRendered(page, { timeout: renderedTimeout, waitVolumeLoad });
|
|
};
|
|
|
|
/**
|
|
* Waits until at least one viewport enters the 'needsRender' state, indicating
|
|
* that a render has been requested but not yet started.
|
|
*/
|
|
const waitForAnyViewportNeedsRender = async (
|
|
page: Page,
|
|
options: WaitForAnyViewportNeedsRenderOptions = {}
|
|
) => {
|
|
const { timeout = 5000 } = options;
|
|
await page.waitForFunction(
|
|
() => {
|
|
const cornerstone = (window as any).cornerstone;
|
|
if (!cornerstone?.getRenderingEngines) {
|
|
return false;
|
|
}
|
|
|
|
const renderingEngines = cornerstone.getRenderingEngines();
|
|
const viewports = renderingEngines.flatMap(engine =>
|
|
engine.getViewports ? engine.getViewports() : []
|
|
);
|
|
|
|
if (!viewports.length) {
|
|
return false;
|
|
}
|
|
|
|
const needsRender = viewports.some(viewport => viewport?.viewportStatus === 'needsRender');
|
|
|
|
return needsRender;
|
|
},
|
|
{},
|
|
{ timeout }
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Stabilize tests by waiting for a short tick, network idle, then viewport render completion.
|
|
* To use this method safely, you may need to make changes to OHIF and/or CS3D
|
|
* methods handling clicks (SHOULD be commands modules only). These should set the
|
|
* state to needs render synchronously so that this method can safely wait for the render to complete.
|
|
* Examples such as changing the hanging protocol currently don't set such a state
|
|
* and thus can't be rendered without a delay.
|
|
*
|
|
* If options.waitVolumeLoad is not false, then this method will wait for all volumes
|
|
* associated with viewports to be loaded.
|
|
*/
|
|
const waitForViewportsRendered = async (
|
|
page: Page,
|
|
options: WaitForViewportsRenderedOptions = {}
|
|
) => {
|
|
const { timeout = 15000, waitVolumeLoad = true } = options;
|
|
|
|
await page.waitForFunction(
|
|
({ waitVolumeLoad }) => {
|
|
const cornerstone = (window as any).cornerstone;
|
|
if (!cornerstone?.getRenderingEngines) {
|
|
return false;
|
|
}
|
|
|
|
const renderingEngines = cornerstone.getRenderingEngines();
|
|
const viewports = renderingEngines.flatMap(engine =>
|
|
engine.getViewports ? engine.getViewports() : []
|
|
);
|
|
|
|
if (!viewports.length) {
|
|
return false;
|
|
}
|
|
|
|
const allRendered = viewports.every(viewport => viewport?.viewportStatus === 'rendered');
|
|
|
|
if (!allRendered) {
|
|
return false;
|
|
}
|
|
|
|
if (!waitVolumeLoad) {
|
|
return true;
|
|
}
|
|
|
|
const cache = cornerstone.cache;
|
|
if (!cache?.getVolume) {
|
|
return true;
|
|
}
|
|
|
|
const actorEntries = viewports.flatMap(viewport =>
|
|
viewport?.getActors ? viewport.getActors() : []
|
|
);
|
|
|
|
for (const actorEntry of actorEntries) {
|
|
const id = actorEntry?.referencedId || actorEntry?.uid;
|
|
if (!id) {
|
|
continue;
|
|
}
|
|
|
|
let volume: any;
|
|
try {
|
|
volume = cache.getVolume(id);
|
|
} catch {
|
|
continue;
|
|
}
|
|
|
|
const loaded =
|
|
volume?.loadStatus && typeof volume.loadStatus.loaded === 'boolean'
|
|
? volume.loadStatus.loaded
|
|
: true;
|
|
|
|
if (!loaded) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
{ waitVolumeLoad },
|
|
{ timeout }
|
|
);
|
|
};
|
|
|
|
export { waitForViewportsRendered, waitForAnyViewportNeedsRender, waitForViewportRenderCycle };
|