diff --git a/tests/baseline/layers_max_opacity_2d_view-chrome-1.png b/tests/baseline/layers_max_opacity_2d_view-chrome-1.png index bd7541f27..d21362f57 100644 Binary files a/tests/baseline/layers_max_opacity_2d_view-chrome-1.png and b/tests/baseline/layers_max_opacity_2d_view-chrome-1.png differ diff --git a/tests/fixtures/layering/mra-head-neck-3-slices.zip b/tests/fixtures/layering/mra-head-neck-3-slices.zip new file mode 100644 index 000000000..48f86eb91 Binary files /dev/null and b/tests/fixtures/layering/mra-head-neck-3-slices.zip differ diff --git a/tests/fixtures/layering/prostate-3-slices.zip b/tests/fixtures/layering/prostate-3-slices.zip new file mode 100644 index 000000000..20b5a91ae Binary files /dev/null and b/tests/fixtures/layering/prostate-3-slices.zip differ diff --git a/tests/specs/layers-and-rendering.e2e.ts b/tests/specs/layers-and-rendering.e2e.ts index 66ecd9d04..a67be5fd0 100644 --- a/tests/specs/layers-and-rendering.e2e.ts +++ b/tests/specs/layers-and-rendering.e2e.ts @@ -1,7 +1,32 @@ -import { DOWNLOAD_TIMEOUT } from '@/wdio.shared.conf'; +import * as fs from 'fs'; +import * as path from 'path'; +import { cleanuptotal } from 'wdio-cleanuptotal-service'; +import { DOWNLOAD_TIMEOUT, FIXTURES, TEMP_DIR } from '@/wdio.shared.conf'; import { volViewPage } from '../pageobjects/volview.page'; -import { openUrls } from './utils'; -import { PROSTATEX_DATASET, MRA_HEAD_NECK_DATASET } from './configTestUtils'; +import { openUrls, openVolViewPage, writeManifestToFile } from './utils'; +import { PROSTATEX_DATASET } from './configTestUtils'; + +const openLayeringFixtures = async () => { + const fixtureNames = ['prostate-3-slices.zip', 'mra-head-neck-3-slices.zip']; + await Promise.all( + fixtureNames.map(async (name) => { + const target = path.join(TEMP_DIR, name); + await fs.promises.copyFile(path.join(FIXTURES, 'layering', name), target); + cleanuptotal.addCleanup(async () => { + fs.unlinkSync(target); + }); + }) + ); + + const manifestName = `layering-fixtures-${Date.now()}.json`; + await writeManifestToFile( + { + resources: fixtureNames.map((name) => ({ url: `/tmp/${name}` })), + }, + manifestName + ); + await openVolViewPage(manifestName); +}; describe('Layers and Rendering', () => { it('should show 3D rendering controls regardless of active view', async () => { @@ -33,7 +58,7 @@ describe('Layers and Rendering', () => { }); it('should create overlay with 2 DICOM images', async () => { - await openUrls([PROSTATEX_DATASET, MRA_HEAD_NECK_DATASET]); + await openLayeringFixtures(); await browser.waitUntil( async () => { @@ -46,6 +71,9 @@ describe('Layers and Rendering', () => { } ); + const mraCard = await $('.volume-card[html-title^="**TOF"]'); + await mraCard.click(); + const inactiveMenuSelector = '.volume-card:not(.volume-card-active) button[data-testid="dataset-menu-button"]'; @@ -87,6 +115,23 @@ describe('Layers and Rendering', () => { ); await addLayerButton.click(); + await browser.waitUntil( + () => + browser.execute(() => { + const app = (document.querySelector('#app') as any)?.__vue_app__; + const pinia = app?.config?.globalProperties?.$pinia; + const imageCache = pinia?._s?.get('image-cache'); + const layerId = imageCache?.imageIds?.find((id: string) => + id.includes('::') + ); + return layerId && imageCache.imageStatus[layerId] === 'complete'; + }), + { + timeout: DOWNLOAD_TIMEOUT, + timeoutMsg: 'Expected the layer image to finish loading', + } + ); + const renderTab = await volViewPage.renderingModuleTab; await renderTab.click(); @@ -103,36 +148,57 @@ describe('Layers and Rendering', () => { const layerOpacitySliders = await volViewPage.layerOpacitySliders; const firstSlider = layerOpacitySliders[0]; + const sliderThumb = await firstSlider.$('[role="slider"]'); + const inputElement = await firstSlider.$('input'); + const views2D = await volViewPage.getViews2D(); + const viewCount = await views2D.length; + if (!views2D || viewCount === 0) { + throw new Error('Could not find 2D views for screenshot'); + } + const firstView2D = views2D[0]; + const beforeOpacityChange = await firstView2D.takeScreenshot(); - await firstSlider.click(); + await browser.execute((element) => { + (element as HTMLElement).focus(); + }, sliderThumb); - // Use keyboard to set to maximum (End key goes to max) await browser.keys('End'); await browser.waitUntil( async () => { - const inputElement = await firstSlider.$('input'); const value = await inputElement.getValue(); return value === '1'; }, { - timeoutMsg: 'Expected slider value to be 1 (max opacity)', + timeoutMsg: 'Expected slider value to be 1', timeout: 5000, } ); - const views2D = await volViewPage.getViews2D(); - const viewCount = await views2D.length; - if (!views2D || viewCount === 0) { - throw new Error('Could not find 2D views for screenshot'); - } - const firstView2D = views2D[0]; + await browser.waitUntil( + async () => (await firstView2D.takeScreenshot()) !== beforeOpacityChange, + { + timeout: 5000, + timeoutMsg: 'Expected the 2D view to render the opacity change', + } + ); + const result = await browser.checkElement( firstView2D, - 'layers_max_opacity_2d_view' + 'layers_max_opacity_2d_view', + { + pixelmatch: { threshold: 0.2 }, + // Compare where the resampled prostate layer intersects the MRA. + blockOut: [ + { x: 0, y: 0, width: 371, height: 130 }, + { x: 0, y: 240, width: 371, height: 92 }, + { x: 0, y: 130, width: 20, height: 110 }, + { x: 340, y: 130, width: 31, height: 110 }, + ], + } ); - const THRESHOLD = 5; // percent + const THRESHOLD = 1; // percent await expect(result).toBeLessThan(THRESHOLD); }); });