diff --git a/.circleci/config.yml b/.circleci/config.yml index 927c2e57b..6eeaba736 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -315,6 +315,7 @@ workflows: working_directory: platform/viewer build: npx cross-env QUICK_BUILD=true yarn run build:package start: yarn run test:e2e:serve + spec: 'cypress/integration/common/**/*' wait-on: 'http://localhost:3000' cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' no-workspace: true # Don't persist workspace @@ -365,6 +366,7 @@ workflows: working_directory: platform/viewer build: npx cross-env QUICK_BUILD=true yarn run build:package start: yarn run test:e2e:serve + spec: 'cypress/integration/common/**/*' wait-on: 'http://localhost:3000' cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' no-workspace: true # Don't persist workspace diff --git a/platform/viewer/cypress/integration/common/OHIFCornerstoneToolbar.spec.js b/platform/viewer/cypress/integration/common/OHIFCornerstoneToolbar.spec.js index 0ce8c3560..75611a141 100644 --- a/platform/viewer/cypress/integration/common/OHIFCornerstoneToolbar.spec.js +++ b/platform/viewer/cypress/integration/common/OHIFCornerstoneToolbar.spec.js @@ -2,12 +2,12 @@ describe('OHIF Cornerstone Toolbar', () => { before(() => { cy.openStudy('MISTER^MR'); cy.waitDicomImage(); + cy.expectMinimumThumbnails(1); }); beforeEach(() => { cy.initCornerstoneToolsAliases(); cy.initCommonElementsAliases(); - //Following best practices, reset should be done before each test cy.resetViewport(); }); @@ -145,7 +145,7 @@ describe('OHIF Cornerstone Toolbar', () => { //Add annotation on the viewport const firstClick = [150, 100]; const secondClick = [130, 170]; - cy.addLine('.cornerstone-canvas', firstClick, secondClick); + cy.addLine('@viewport', firstClick, secondClick); //Verify if measurement annotation was added into the measurements panel cy.get('@measurementsBtn') @@ -173,7 +173,7 @@ describe('OHIF Cornerstone Toolbar', () => { const initPos = [180, 390]; const midPos = [300, 410]; const finalPos = [180, 450]; - cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos); + cy.addAngle('@viewport', initPos, midPos, finalPos); //Verify if measurement annotation was added into the measurements panel cy.get('@measurementsBtn') @@ -358,33 +358,20 @@ describe('OHIF Cornerstone Toolbar', () => { .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); - // }) - cy.reload(); - cy.waitDicomImage(); + 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); + }); }); it('checks if Clear tool will delete all measurements added in the viewport', () => { //Add measurements in the viewport - cy.get('@lengthBtn').click(); - const firstClick = [150, 100]; - const secondClick = [130, 170]; - cy.addLine('.cornerstone-canvas', firstClick, secondClick); - - cy.get('@angleBtn').click(); - const initPos = [180, 390]; - const midPos = [300, 410]; - const finalPos = [180, 450]; - cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos); + cy.addLengthMeasurement(); + cy.addAngleMeasurement(); //Verify if measurement annotation was added into the measurements panel cy.get('@measurementsBtn').click(); @@ -416,23 +403,18 @@ describe('OHIF Cornerstone Toolbar', () => { it('checks if Eraser tool will remove the measurements added in the viewport', () => { //Add measurements in the viewport - cy.get('@lengthBtn').click(); - const firstClick = [150, 100]; - const secondClick = [130, 170]; - cy.addLine('.cornerstone-canvas', firstClick, secondClick); - - cy.get('@angleBtn').click(); - const initPos = [180, 390]; - const midPos = [300, 410]; - const finalPos = [180, 450]; - cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos); + cy.addLengthMeasurement(); + cy.addAngleMeasurement(); //Verify if measurement annotation was added into the measurements panel cy.get('@measurementsBtn').click(); cy.get('.measurementItem') .its('length') .should('be.eq', 2); - cy.get('@measurementsBtn').click(); + cy.get('@measurementsBtn') + .click() + .wait(2000); + //cy.isNotInViewport('@measurementsPanel'); //TO DO: check this intermittent behaviour //Click More button cy.get('@moreBtn').click(); @@ -440,14 +422,14 @@ describe('OHIF Cornerstone Toolbar', () => { cy.get('.tooltip-inner > :nth-child(12)').click(); //Erase measurement #1 and Verify if it was removed from the measurements panel - const [x1, y1] = firstClick; + const [x1, y1] = [150, 100]; cy.get('@viewport').click(x1, y1, { force: true }); cy.get('.measurementItem') .its('length') .should('be.eq', 1); //Erase measurement #2 and Verify if it was removed from the measurements panel - const [x2, y2] = initPos; + const [x2, y2] = [180, 390]; cy.get('@viewport').click(x2, y2, { force: true }); cy.get('.measurementItem').should('not.exist'); }); diff --git a/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js b/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js index 16e3cae55..9b735adbd 100644 --- a/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js +++ b/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js @@ -2,11 +2,11 @@ describe('OHIF Study Viewer Page', () => { before(() => { cy.openStudy('MISTER^MR'); cy.waitDicomImage(); + cy.expectMinimumThumbnails(1); }); beforeEach(() => { cy.initCommonElementsAliases(); - //Following best practices, reset should be done before each test cy.resetViewport(); }); @@ -65,8 +65,11 @@ describe('OHIF Study Viewer Page', () => { // Confirm Selection cy.get('.checkIconWrapper').click(); - //Verify if 'Bone' label was added + // Verify if 'Bone' label was added cy.get('.measurementLocation').should('contain.text', 'Bone'); + // Close panel + cy.get('@measurementsBtn').click(); + cy.get('@measurementsPanel').should('not.be.enabled'); }); //TO-DO: Test case will fail due to issue #1013: https://github.com/OHIF/Viewers/issues/1013 @@ -93,4 +96,114 @@ describe('OHIF Study Viewer Page', () => { // cy.get('.measurementLocation') // .should('contain.text', descriptionText); // }); + + it('checks if measurement item can be deleted through the context menu on the viewport', () => { + cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport + + //Right click on measurement annotation + const [x1, y1] = [150, 100]; + cy.get('@viewport') + .trigger('mousedown', x1, y1, { + which: 3, + }) + .trigger('mouseup', x1, y1, { + which: 3, + }); + + //Contextmenu is visible + cy.get('.ToolContextMenu').should('be.visible'); + + //Click "Delete measurement" + cy.get('.form-action') + .contains('Delete measurement') + .click(); + + //Open measurements menu + cy.get('@measurementsBtn').click(); + + //Verify measurements was removed from panel + cy.get('.measurementItem') + .should('not.exist') + .log('Annotation removed with success'); + + //Close panel + cy.get('@measurementsBtn').click(); + cy.get('@measurementsPanel').should('not.be.enabled'); + }); + + it('adds relabel and description to measurement item through the context menu on the viewport', () => { + cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport + + // Relabel + // Right click on measurement annotation + const [x1, y1] = [150, 100]; + cy.get('@viewport') + .trigger('mousedown', x1, y1, { + which: 3, + }) + .trigger('mouseup', x1, y1, { + which: 3, + }); + + // Contextmenu is visible + cy.get('.ToolContextMenu').should('be.visible'); + + // Click "Relabel" + cy.get('.form-action') + .contains('Relabel') + .click(); + + // Search for "Brain" + cy.get('.searchInput').type('Brain'); + + // Select "Brain" Result + cy.get('.treeInputs > .wrapperLabel') + .contains('Brain') + .click(); + + // Confirm Selection + cy.get('.checkIconWrapper').click(); + + // Description + // Right click on measurement annotation + cy.get('@viewport') + .trigger('mousedown', x1, y1, { + which: 3, + }) + .trigger('mouseup', x1, y1, { + which: 3, + }); + + // Contextmenu is visible + cy.get('.ToolContextMenu').should('be.visible'); + + // Click "Description" + cy.get('.form-action') + .contains('Add Description') + .click(); + + // Enter description text + const descriptionText = 'Adding text for description test'; + cy.get('#description').type(descriptionText); + + // Confirm + cy.get('.btn-confirm').click(); + + //Open measurements menu + cy.get('@measurementsBtn').click(); + + // Verify if label was added + cy.get('.measurementLocation') + .should('contain.text', 'Brain') + .log('Relabel added with success'); + + //Verify if descriptionText was added + cy.get('.measurementLocation') + .should('contain.text', descriptionText) + .log('Description added with success'); + + // Close panel + cy.get('@measurementsBtn').click(); + cy.get('@measurementsPanel').should('not.be.enabled'); + }); }); diff --git a/platform/viewer/cypress/integration/pwa/OHIFExtensionHTML.spec.js b/platform/viewer/cypress/integration/pwa/OHIFExtensionHTML.spec.js new file mode 100644 index 000000000..7866efb6f --- /dev/null +++ b/platform/viewer/cypress/integration/pwa/OHIFExtensionHTML.spec.js @@ -0,0 +1,24 @@ +describe('OHIF HTML Extension', () => { + before(() => { + cy.openStudy('Dummy'); + cy.expectMinimumThumbnails(1); + }); + + it('checks if series thumbnails are being displayed', () => { + cy.get('[data-cy="thumbnail-list"]') + .contains('SR') + .its('length') + .should('to.be.at.least', 1); + }); + + it('drags and drop a SR thumbnail into viewport', () => { + cy.get('[data-cy="thumbnail-list"]') + .contains('SR') + .drag('.viewport-drop-target'); + + cy.get(':nth-child(2) > h1').should( + 'contain.text', + 'Imaging Measurement Report' + ); + }); +}); diff --git a/platform/viewer/cypress/integration/pwa/OHIFExtensionPDF.spec.js b/platform/viewer/cypress/integration/pwa/OHIFExtensionPDF.spec.js new file mode 100644 index 000000000..8e840ea1e --- /dev/null +++ b/platform/viewer/cypress/integration/pwa/OHIFExtensionPDF.spec.js @@ -0,0 +1,23 @@ +describe('OHIF PDF Extension', () => { + before(() => { + cy.openStudy('Dummy'); + cy.expectMinimumThumbnails(1); + }); + + it('checks if series thumbnails are being displayed', () => { + cy.get('[data-cy="thumbnail-list"]') + .contains('DOC') + .its('length') + .should('to.be.at.least', 1); + }); + + it('drags and drop a PDF thumbnail into viewport', () => { + cy.get('[data-cy="thumbnail-list"]') + .contains('DOC') + .drag('.viewport-drop-target'); + + cy.get('.DicomPDFViewport') + .its('length') + .should('be.eq', 1); + }); +}); diff --git a/platform/viewer/cypress/integration/pwa/OHIFExtensionVTK.spec.js b/platform/viewer/cypress/integration/pwa/OHIFExtensionVTK.spec.js new file mode 100644 index 000000000..ac6b77e6c --- /dev/null +++ b/platform/viewer/cypress/integration/pwa/OHIFExtensionVTK.spec.js @@ -0,0 +1,47 @@ +describe('OHIF VTK Extension', () => { + before(() => { + cy.openStudy('Juno'); + cy.waitDicomImage(); + cy.expectMinimumThumbnails(1); + }); + + beforeEach(() => { + // TODO: We shouldn't have to drag the thumbnail + // This is a known bug; 2D MPR button does not show until viewport + // has data from a drag-n-drop + // Drag and drop first thumbnail into first viewport + cy.get('[data-cy="thumbnail-list"]:nth-child(3)').drag( + '.cornerstone-canvas' + ); + + cy.get('.PluginSwitch > .toolbar-button') + .as('twodmprBtn') + .should('be.visible') + .click(); + + cy.initVTKToolsAliases(); + cy.wait(1000); + }); + + it('checks if VTK buttons are displayed on the toolbar', () => { + cy.get('@crosshairsBtn') + .should('be.visible') + .contains('Crosshairs'); + cy.get('@wwwcBtn') + .should('be.visible') + .contains('WWWC'); + cy.get('@rotateBtn') + .should('be.visible') + .contains('Rotate'); + cy.get('@slabSlider') + .should('be.visible') + .contains('Slab Thickness'); + cy.get('@modeDropdown') + .should('be.visible') + .contains('MIP'); + cy.get('@modeCheckbox').should('be.visible'); + cy.get('@layoutBtn') + .should('be.visible') + .contains('Layout'); + }); +}); diff --git a/platform/viewer/cypress/support/aliases.js b/platform/viewer/cypress/support/aliases.js index 37fa12ec2..d040dc09c 100644 --- a/platform/viewer/cypress/support/aliases.js +++ b/platform/viewer/cypress/support/aliases.js @@ -31,3 +31,21 @@ export function initCommonElementsAliases() { 'viewportInfoBottomRight' ); } + +//Creating aliases for Routes +export function initRouteAliases() { + cy.server(); + cy.route('GET', '/dcm4chee-arc/**/series').as('getStudySeries'); +} + +//Creating aliases for VTK tools buttons +export function initVTKToolsAliases() { + cy.get('.PluginSwitch > .toolbar-button').as('twodmprBtn'); + cy.get('.ToolbarRow > :nth-child(2)').as('crosshairsBtn'); + cy.get('.ToolbarRow > :nth-child(3)').as('wwwcBtn'); + cy.get('.ToolbarRow > :nth-child(4)').as('rotateBtn'); + cy.get('.slab-thickness').as('slabSlider'); + cy.get('.select-ohif').as('modeDropdown'); + cy.get('.ohif-check-label').as('modeCheckbox'); + cy.get('.btn-group > .toolbar-button').as('layoutBtn'); +} diff --git a/platform/viewer/cypress/support/commands.js b/platform/viewer/cypress/support/commands.js index d0681efc9..95140ab21 100644 --- a/platform/viewer/cypress/support/commands.js +++ b/platform/viewer/cypress/support/commands.js @@ -3,6 +3,8 @@ import { DragSimulator } from '../helpers/DragSimulator.js'; import { initCornerstoneToolsAliases, initCommonElementsAliases, + initRouteAliases, + initVTKToolsAliases, } from './aliases.js'; // *********************************************** @@ -37,14 +39,19 @@ import { * @param {string} PatientName - Patient name that we would like to search for */ Cypress.Commands.add('openStudy', patientName => { + cy.initRouteAliases(); cy.visit('/'); cy.get('#patientName').type(patientName); - cy.get('.studylistStudy > .patientName') - .as('patientResult') - .then({ timeout: 5000 }, $patientResult => { - cy.contains(patientName).click(); - }); + cy.get('.studylistStudy > .patientName', { timeout: 5000 }) + .contains(patientName) + .click(); + + // cy.get('.studylistStudy > .patientName') + // .as('patientResult') + // .should({ timeout: 3000 }, () => { + // cy.contains(patientName).click(); + // }); }); /** @@ -104,6 +111,12 @@ Cypress.Commands.add( } ); +Cypress.Commands.add('expectMinimumThumbnails', (seriesToWait = 1) => { + cy.get('[data-cy=thumbnail-list]', { timeout: 10000 }).should($itemList => { + expect($itemList.length >= seriesToWait).to.be.true; + }); +}); + //Command to wait DICOM image to load into the viewport Cypress.Commands.add('waitDicomImage', (timeout = 20000) => { cy.window() @@ -183,21 +196,82 @@ Cypress.Commands.add('initCommonElementsAliases', () => { initCommonElementsAliases(); }); -//Add measurements in the viewport -Cypress.Commands.add('addLengthMeasurement', () => { - cy.initCornerstoneToolsAliases(); - cy.get('@lengthBtn').click(); - const firstClick = [150, 100]; - const secondClick = [130, 170]; - cy.addLine('.cornerstone-canvas', firstClick, secondClick); +//Initialize aliases for Routes +Cypress.Commands.add('initRouteAliases', () => { + initRouteAliases(); +}); + +//Initialize aliases for VTK tools +Cypress.Commands.add('initVTKToolsAliases', () => { + initVTKToolsAliases(); }); //Add measurements in the viewport -Cypress.Commands.add('addAngleMeasurement', () => { - cy.initCornerstoneToolsAliases(); - cy.get('@angleBtn').click(); - const initPos = [180, 390]; - const midPos = [300, 410]; - const finalPos = [180, 450]; - cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos); +Cypress.Commands.add( + 'addLengthMeasurement', + (firstClick = [150, 100], secondClick = [130, 170]) => { + cy.initCornerstoneToolsAliases(); + cy.get('@lengthBtn').click(); + cy.addLine('@viewport', firstClick, secondClick); + } +); + +//Add measurements in the viewport +Cypress.Commands.add( + 'addAngleMeasurement', + (initPos = [180, 390], midPos = [300, 410], finalPos = [180, 450]) => { + cy.initCornerstoneToolsAliases(); + cy.get('@angleBtn').click(); + cy.addAngle('@viewport', initPos, midPos, finalPos); + } +); + +/** + * Tests if element is NOT in viewport, or does not exist in DOM + * + * @param {string} element - element selector string or alias + * @returns + */ +Cypress.Commands.add('isNotInViewport', element => { + cy.get(element, { timeout: 3000 }).should($el => { + const bottom = Cypress.$(cy.state('window')).height() - 50; + const right = Cypress.$(cy.state('window')).width() - 50; + + // If it's not visible, it's not in the viewport + if ($el) { + const rect = $el[0].getBoundingClientRect(); + + // TODO: support leftOf, above + const isBeneath = rect.top >= bottom && rect.bottom >= bottom; + const isRightOf = rect.left >= right && rect.right >= right; + const isNotInViewport = isBeneath && isRightOf; + + expect(isNotInViewport).to.be.true; + } + }); +}); + +/** + * Tests if element is in viewport, or it does exist in DOM + * + * @param {string} element - element selector string or alias + * @returns + */ +Cypress.Commands.add('isInViewport', element => { + cy.get(element, { timeout: 3000 }).should($el => { + const bottom = Cypress.$(cy.state('window')).height(); + const right = Cypress.$(cy.state('window')).width(); + + // If it's not visible, it's not in the viewport + if ($el) { + const rect = $el[0].getBoundingClientRect(); + + // TODO: support leftOf, above + const isBeneath = rect.top < bottom && rect.bottom < bottom; + const isRightOf = rect.left < right && rect.right < right; + const isInViewport = isBeneath && isRightOf; + + expect(isInViewport).to.be.true; + } + }); });