From c92975f555250f6d8fa71aac9d7b4069c79e464d Mon Sep 17 00:00:00 2001 From: Mirna Silva Date: Sun, 6 Oct 2019 21:57:49 -0300 Subject: [PATCH] test: Fix for test scripts that were failing in CI (#968) * Fix for test scripts that were failing in CI * Test cases for tools: Length and Angle * Test cases implemented: Scroll series, Levels, Pan, Reset, Zoom * Added test cases for tools: Cine, More, Layout, Invert, Rotate, FlipH, FlipV * Fixes made according to comments on PR review * Fixes for failing scenarios: Layout and Cine --- .../common/OHIFCornerstoneToolbar.spec.js | 407 ++++++++++++++++++ .../common/OHIFStudyViewer.spec.js | 5 +- platform/viewer/cypress/support/commands.js | 110 ++++- 3 files changed, 515 insertions(+), 7 deletions(-) create mode 100644 platform/viewer/cypress/integration/common/OHIFCornerstoneToolbar.spec.js diff --git a/platform/viewer/cypress/integration/common/OHIFCornerstoneToolbar.spec.js b/platform/viewer/cypress/integration/common/OHIFCornerstoneToolbar.spec.js new file mode 100644 index 000000000..bdedf572e --- /dev/null +++ b/platform/viewer/cypress/integration/common/OHIFCornerstoneToolbar.spec.js @@ -0,0 +1,407 @@ +describe('OHIF Cornerstone Toolbar', () => { + + before(() => { + cy.openStudy("MISTER^MR"); + cy.waitDicomImage(); + }); + + beforeEach(() => { + //Creating aliases for Cornerstone tools buttons + cy.get('.toolbar-button:nth-child(2)').as('stackScrollBtn'); + cy.get('.toolbar-button:nth-child(3)').as('zoomBtn'); + cy.get('.toolbar-button:nth-child(4)').as('levelsBtn'); + cy.get('.toolbar-button:nth-child(5)').as('panBtn'); + cy.get('.toolbar-button:nth-child(6)').as('lengthBtn'); + cy.get('.toolbar-button:nth-child(7)').as('annotateBtn'); + cy.get('.toolbar-button:nth-child(8)').as('angleBtn'); + cy.get('.toolbar-button:nth-child(9)').as('resetBtn'); + cy.get('.toolbar-button:nth-child(10)').as('cineBtn'); + cy.get('.expandableToolMenu').as('moreBtn'); + cy.get('.PluginSwitch > .toolbar-button').as('twodmprBtn'); + cy.get('.btn-group > .toolbar-button').as('layoutBtn'); + cy.get('.pull-right > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton').as('measurementsBtn'); + cy.get('.viewport-element').as('viewport'); + cy.get('section.sidepanel.from-right').as('measurementsPanel') + //Following best practices, reset should be done before each test + cy.resetViewport(); + }) + + + it('checks if all primary buttons are being displayed', () => { + cy.get('@stackScrollBtn').should('be.visible').contains('Stack Scroll'); + cy.get('@zoomBtn').should('be.visible').contains('Zoom'); + cy.get('@levelsBtn').should('be.visible').contains('Levels'); + cy.get('@panBtn').should('be.visible').contains('Pan'); + cy.get('@lengthBtn').should('be.visible').contains('Length'); + cy.get('@annotateBtn').should('be.visible').contains('Annotate'); + cy.get('@angleBtn').should('be.visible').contains('Angle'); + cy.get('@resetBtn').should('be.visible').contains('Reset'); + cy.get('@cineBtn').should('be.visible').contains('CINE'); + cy.get('@moreBtn').should('be.visible').contains('More'); + cy.get('@twodmprBtn').should('be.visible').contains('2D MPR'); + cy.get('@layoutBtn').should('be.visible').contains('Layout'); + }); + + + it('checks if Stack Scroll tool will navigate across all series in the viewport', () => { + //Click on button and vefiry if icon is active on toolbar + cy.get('@stackScrollBtn') + .click() + .then(($stackScrollBtn) =>{ + cy.wrap($stackScrollBtn) + .should('have.class', 'active') + }) + + //drags the mouse inside the viewport to be able to interact with series + cy.get('@viewport') + .trigger('mousedown', 'top', { which: 1 }) + .trigger('mousemove', 'center', { which: 1 }) + .trigger('mouseup'); + + const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-left.overlay-element > div'; + const expectedText = 'Ser: 1Img: 14 14/26256 x 256Loc: 0.00 mm Thick: 5.00 mm'; + cy.get(overlaySeriesInformation) + .should('have.text', expectedText); + }); + + + it('checks if Zoom tool will zoom in/out an image in the viewport', () => { + //Click on button and vefiry if icon is active on toolbar + cy.get('@zoomBtn') + .click() + .then(($zoomBtn) =>{ + cy.wrap($zoomBtn) + .should('have.class', 'active') + }) + + //drags the mouse inside the viewport to be able to interact with series + cy.get('@viewport') + .trigger('mousedown', 'top', { which: 1 }) + .trigger('mousemove', 'center', { which: 1 }) + .trigger('mouseup'); + + const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-right.overlay-element > div'; + const expectedText = 'Zoom: 884%W: 820 L: 410Lossless / Uncompressed'; + cy.get(overlaySeriesInformation) + .should('have.text', expectedText); + }); + + + it('checks if Levels tool will change the contrast and brightness of an image in the viewport', () => { + //Click on button and vefiry if icon is active on toolbar + cy.get('@levelsBtn') + .click() + .then(($levelsBtn) =>{ + cy.wrap($levelsBtn) + .should('have.class', 'active') + }) + + //drags the mouse inside the viewport to be able to interact with series + cy.get('@viewport') + .trigger('mousedown', 'top', { which: 1 }) + .trigger('mousemove', 'center', { which: 1 }) + .trigger('mouseup') + .trigger('mousedown', 'center', { which: 1 }) + .trigger('mousemove', 'left', { which: 1 }) + .trigger('mouseup'); + + const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-right.overlay-element > div'; + const expectedText = 'Zoom: 211%W: 544 L: 626Lossless / Uncompressed'; + cy.get(overlaySeriesInformation) + .should('have.text', expectedText); + }); + + + it('checks if Pan tool will move the image inside the viewport', () => { + //Click on button and vefiry if icon is active on toolbar + cy.get('@panBtn') + .click() + .then(($panBtn) =>{ + cy.wrap($panBtn) + .should('have.class', 'active') + }) + + //Get image position from cornerstone and check if y axis was modified + let cornerstone; + let currentPan; + + cy.window() + .its('cornerstone') + .then((c) => { + cornerstone = c; + currentPan = () => cornerstone.getEnabledElements()[0].viewport.translation; + }); + + cy.get('@viewport') + .trigger('mousedown', 'center', { which: 1 }) + .trigger('mousemove', 'bottom', { which: 1 }) + .trigger('mouseup', 'bottom') + .then(() => { + expect(currentPan().y > 0).to.eq(true); + }); + }); + + + it('checks if Length annotation can be added on viewport and on measurements panel', () => { + //Click on button and vefiry if icon is active on toolbar + cy.get('@lengthBtn') + .click() + .then(($lengthbtn) =>{ + cy.wrap($lengthbtn) + .should('have.class', 'active') + }) + + //Add annotation on the viewport + const firstClick = [150, 100]; + const secondClick = [130, 170]; + cy.addLine('.cornerstone-canvas', firstClick, secondClick) + + //Verify if measurement annotation was added into the measurements panel + cy.get('@measurementsBtn') + .click() + .then($measurementsBtn => { + cy.get('@measurementsPanel') + .should('be.visible'); + + cy.get('.measurementItem') + .its('length') + .should('be.eq', 1); + + cy.wrap($measurementsBtn) + .click(); + }) + }); + + + it('checks if Angle annotation can be added on viewport and on measurements panel', () => { + //Click on button and vefiry if icon is active on toolbar + cy.get('@angleBtn') + .click() + .then(($angleBtn) =>{ + cy.wrap($angleBtn) + .should('have.class', 'active') + }) + + //Add annotation on the viewport + const initPos = [180, 390]; + const midPos = [300, 410]; + const finalPos = [180, 450]; + cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos); + + //Verify if measurement annotation was added into the measurements panel + cy.get('@measurementsBtn') + .click() + .then($measurementsBtn => { + cy.get('@measurementsPanel') + .should('be.visible'); + + cy.get('.measurementItem') + .its('length') + .should('be.eq', 2); + + cy.wrap($measurementsBtn) + .click(); + }) + }); + + + it('checks if Reset tool will reset all changes made on the image', () => { + //Make some changes by zooming in and rotating the image + cy.imageZoomIn(); + cy.imageContrast(); + + //Click on reset button + cy.get('@resetBtn').click() + + const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-right.overlay-element > div'; + const expectedText = 'Zoom: 211%W: 820 L: 410Lossless / Uncompressed'; + cy.get(overlaySeriesInformation) + .should('have.text', expectedText); + }); + + + it('checks if CINE tool will prompt a modal with working controls', () => { + //Click on button + cy.get('@cineBtn') + .click(); + //Vefiry if cine control overlay is being displayed + cy.get('.cine-controls').as('cineControls') + .should('be.visible'); + + //Test PLAY button + cy.get('[title="Play / Stop"]') + .click() + .wait(100) + .click(); + + const overlaySeriesInformation = 'div.ViewportOverlay > div.bottom-left.overlay-element > div'; + let expectedText = 'Img: 1 1/26'; + cy.get(overlaySeriesInformation) + .should('not.have.text', expectedText); + + //Test SKIP TO FIRST IMAGE button + cy.get('[title="Skip to first Image"]') + .click() + cy.get(overlaySeriesInformation) + .should('contain.text', expectedText); + + //Test NEXT IMAGE button + cy.get('[title="Next Image"]') //Title is wrong and was reported on bug #995: https://github.com/OHIF/Viewers/issues/995 + .click() + expectedText = 'Img: 2 2/26'; + cy.get(overlaySeriesInformation) + .should('contain.text', expectedText); + + //Test SKIP TO LAST IMAGE button + cy.get('[title="Skip to last Image"]') //Title is wrong and was reported on bug #995: https://github.com/OHIF/Viewers/issues/995 + .click() + expectedText = 'Img: 27 26/26'; + cy.get(overlaySeriesInformation) + .should('contain.text', expectedText); + + //Test PREVIOUS IMAGE button + cy.get('[title="Previous Image"]') + .click() + expectedText = 'Img: 26 25/26'; + cy.get(overlaySeriesInformation) + .should('contain.text', expectedText); + + //Click on Cine button + cy.get('@cineBtn') + .click(); + //Vefiry if cine control overlay is hidden + cy.get('@cineControls') + .should('not.be.visible'); + }); + + + it('checks if More button will prompt a modal with secondary tools', () => { + cy.get('@moreBtn') + .click(); + //Verify if overlay is displayed + cy.get('.tooltip-toolbar-overlay').as('toolbarOverlay') + .should('be.visible'); + + let iconName; + //Click on one of the secondary tools from the overlay + cy.get('.tooltip-inner > :nth-child(1)') + .click() + .then(($magnifyBtn) =>{ + cy.wrap($magnifyBtn) + .should('have.class', 'active') + .find('svg').then(($icon)=>{ + iconName = $icon.text(); + }) + }) + + //Check if More button is active and if it has same icon as the secondary tool selected + cy.get('@moreBtn') + .click() + .then(($moreBtn) =>{ + cy.wrap($moreBtn) + .should('have.class', 'active') + .contains(iconName); + }) + + //Verify if overlay is hidden + cy.get('@toolbarOverlay') + .should('not.be.visible'); + }); + + + it('checks if Layout tool will multiply the number of viewports displayed', () => { + //Click on Layout button and verify if overlay is displayed + cy.get('@layoutBtn') + .click() + .then(() => { + cy.get('.layoutChooser').as('layoutChooser') + .should('be.visible') + .find('td') + .its('length') + .should('be.eq', 9); + }) + + //verify if layout has changed to 2 viewports + cy.get('tbody > :nth-child(1) > :nth-child(2)').click(); + cy.get('.viewport-container').then(($viewport) =>{ + cy.wrap($viewport) + .its('length') + .should('be.eq', 2); + }) + + cy.get('@layoutBtn').click(); + cy.get('tbody > :nth-child(2) > :nth-child(1)').click(); + cy.get('.viewport-container').then(($viewport) =>{ + cy.wrap($viewport) + .its('length') + .should('be.eq', 2); + }) + + //verify if layout has changed to 3 viewports + cy.get('@layoutBtn').click(); + cy.get('tbody > :nth-child(1) > :nth-child(3)').click(); + cy.get('.viewport-container').then(($viewport) =>{ + cy.wait(1000); + cy.wrap($viewport) + .its('length') + .should('be.eq', 3); + }) + + cy.get('@layoutBtn').click(); + cy.get('tbody > :nth-child(3) > :nth-child(1)').click(); + cy.get('.viewport-container').then(($viewport) =>{ + cy.wrap($viewport) + .its('length') + .should('be.eq', 3); + }) + + //verify if layout has changed to 4 viewports + cy.get('@layoutBtn').click(); + cy.get('tbody > :nth-child(2) > :nth-child(2)').click(); + cy.get('.viewport-container').then(($viewport) =>{ + cy.wrap($viewport) + .its('length') + .should('be.eq', 4); + }) + + //verify if layout has changed to 6 viewports + cy.get('@layoutBtn').click(); + cy.get('tbody > :nth-child(2) > :nth-child(3)').click(); + cy.get('.viewport-container').then(($viewport) =>{ + cy.wrap($viewport) + .its('length') + .should('be.eq', 6); + }) + + cy.get('@layoutBtn').click(); + cy.get('tbody > :nth-child(3) > :nth-child(2)').click(); + cy.get('.viewport-container').then(($viewport) =>{ + cy.wrap($viewport) + .its('length') + .should('be.eq', 6); + }) + + //verify if layout has changed to 9 viewports + cy.get('@layoutBtn').click(); + cy.get('tbody > :nth-child(3) > :nth-child(3)').click(); + cy.get('.viewport-container').then(($viewport) =>{ + cy.wrap($viewport) + .its('length') + .should('be.eq', 9); + }) + + //Commented this to avoid throwing an wrong exception at the end of "Layout button" test + //this commented section should be uncommented once issue #999 is fixed. (https://github.com/OHIF/Viewers/issues/999) + + //verify if layout has changed to 1 viewport + // cy.get('@layoutBtn').click(); + // cy.get('tbody > :nth-child(1) > :nth-child(1)').click(); + // cy.get('.viewport-container').then(($viewport) =>{ + // cy.wrap($viewport) + // .its('length') + // .should('be.eq', 1); + // }) + + }); + + }); \ No newline at end of file diff --git a/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js b/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js index 8e79ecc9c..ae5c45758 100644 --- a/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js +++ b/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js @@ -1,5 +1,4 @@ -/* -TODO: Temporarily commented out because it is failing on CI due to timing issues + describe('OHIF Study Viewer Page', () => { before(() => { @@ -41,10 +40,8 @@ describe('OHIF Study Viewer Page', () => { cy.get(measurementsButton).click(); cy.get(rightPanel).should('be.visible'); - cy.get(measurementsButton).click(); cy.get(rightPanel).should('not.be.enabled'); }); }); -*/ \ No newline at end of file diff --git a/platform/viewer/cypress/support/commands.js b/platform/viewer/cypress/support/commands.js index 1d0951113..30ab1b60b 100644 --- a/platform/viewer/cypress/support/commands.js +++ b/platform/viewer/cypress/support/commands.js @@ -1,4 +1,6 @@ import { DragSimulator } from "../helpers/DragSimulator.js"; +import { doesNotReject } from "assert"; +import { disconnect } from "cluster"; // *********************************************** // This example commands.js shows you how to @@ -30,12 +32,114 @@ Cypress.Commands.add('openStudy', (patientName) => { cy.visit('/'); cy.get('#patientName') .type(patientName); - cy.get('.studylistStudy > .patientName') - .contains(patientName) - .click(); + + cy.get('.studylistStudy > .patientName').as('patientResult') + .then({ timeout: 5000 }, ($patientResult) => { + cy.contains(patientName) + .click(); + }) } ); Cypress.Commands.add('drag', {prevSubject: 'element',}, (...args) => DragSimulator.simulate(...args) ); + +/** + * Command to perform two clicks into two different positions. Each position must be [x, y]. + * The positions are considering the element as reference, therefore, top-left of the element will be (0, 0). + * + * @param {*} viewport - Selector for viewport we would like to interact with + * @param {number[]} firstClick - Click position [x, y] + * @param {number[]} secondClick - Click position [x, y] + */ +Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => { + + cy.get(viewport).then(($viewport) => { + const [ x1, y1 ] = firstClick; + const [ x2, y2 ] = secondClick; + + cy.wrap($viewport) + .click(x1, y1, {force: true}) + .trigger('mousemove', {clientX:x2, clientY:y2}) + .click(x2, y2, {force: true}) + }); +}); + + +/** + * Command to perform three clicks into three different positions. Each position must be [x, y]. + * The positions are considering the element as reference, therefore, top-left of the element will be (0, 0). + * + * @param {*} viewport - Selector for viewport we would like to interact with + * @param {number[]} firstClick - Click position [x, y] + * @param {number[]} secondClick - Click position [x, y] + * @param {number[]} thirdClick - Click position [x, y] + */ +Cypress.Commands.add('addAngle', (viewport, firstClick, secondClick, thirdClick) => { + + cy.get(viewport).then(($viewport) => { + const [ x1, y1 ] = firstClick; + const [ x2, y2 ] = secondClick; + const [ x3, y3 ] = thirdClick; + + cy.wrap($viewport) + .click(x1, y1, {force: true}) + .trigger('mousemove', {clientX:x2, clientY:y2}) + .click(x2, y2, {force: true}) + .trigger('mousemove', {clientX:x3, clientY:y3}) + .click(x3, y3, {force: true}) + }); +}); + + +//Command to wait DICOM image to load into the viewport +Cypress.Commands.add('waitDicomImage', (timeout = 10000) => { + cy.window() + .its('cornerstone') + .then($cornerstone => { + return new Cypress.Promise(resolve => { + const onEvent = (renderedEvt) => { + const element = renderedEvt.detail.element; + + element.removeEventListener('cornerstoneimagerendered', onEvent); + $cornerstone.events.removeEventListener('cornerstoneimagerendered', onEvent); + resolve(); + } + const onEnabled = (enabledEvt) => { + const element = enabledEvt.detail.element; + + element.addEventListener('cornerstoneimagerendered', onEvent); + } + $cornerstone.events.addEventListener('cornerstoneelementenabled', onEnabled); + }); + }); + +}); + + +//Command to reset the viewport changes throught the cornerstone method +Cypress.Commands.add('resetViewport', () => { + cy.get('@resetBtn').click() +}); + + +Cypress.Commands.add('imageZoomIn', () => { + cy.get('@zoomBtn').click(); + + //drags the mouse inside the viewport to be able to interact with series + cy.get('@viewport') + .trigger('mousedown', 'top', { which: 1 }) + .trigger('mousemove', 'center', { which: 1 }) + .trigger('mouseup'); +}); + +Cypress.Commands.add('imageContrast', () => { + cy.get('@levelsBtn').click(); + + //drags the mouse inside the viewport to be able to interact with series + cy.get('@viewport') + .trigger('mousedown', 'center', { which: 1 }) + .trigger('mousemove', 'top', { which: 1 }) + .trigger('mouseup'); +}); \ No newline at end of file