diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 500d8035fd9c241b9bc56ef90c428b7c99209e1f..c5cec5a833039b574591fec54deb1a73a24bcc03 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -12,6 +12,8 @@ env: DOCKER_E2E_PPTR: gha-iav-e2e-pptr-${{ github.sha }} DOCKER_E2E_NETWORK: gha-dkr-network ATLAS_URL: http://gha-iav-built-${{ github.sha }}:3000/ + CHROMIUM_VERSION: "80.0.3987.106" + PPTR_VERSION: "2.1.0" jobs: buildimage: @@ -58,8 +60,8 @@ jobs: docker cp . ${DOCKER_E2E_PPTR}:/iav docker exec -u root ${DOCKER_E2E_PPTR} chown -R pptruser:pptruser /iav docker exec -t -w /iav ${DOCKER_E2E_PPTR} npm i - docker exec -t -w /iav ${DOCKER_E2E_PPTR} npm run wd -- update --versions.chrome latest - docker exec -t ${DOCKER_E2E_PPTR} npm i puppeteer + docker exec -t -w /iav ${DOCKER_E2E_PPTR} npm run wd -- update --versions.chrome=${{ env.CHROMIUM_VERSION }} + docker exec -t ${DOCKER_E2E_PPTR} npm i --no-save puppeteer@${{ env.PPTR_VERSION }} - name: Setup docker network run: | docker network connect ${{ env.DOCKER_E2E_NETWORK }} ${{ env.DOCKER_E2E_PPTR }} diff --git a/e2e/chromeOpts.js b/e2e/chromeOpts.js index b6900fed9e9fd916c9ffec56222e5b8afa5dea35..eccfe512b1c0bd422f441252f3b5dc70431ab590 100644 --- a/e2e/chromeOpts.js +++ b/e2e/chromeOpts.js @@ -4,5 +4,5 @@ module.exports = [ '--disable-gpu', '--disable-setuid-sandbox', "--disable-extensions", - '--window-size=1600,900' + '--window-size=800,796' ] \ No newline at end of file diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js index 59fdb6a18bc9fb074e53db97076b0b9c56b3d7f9..bf84a28140cb73eed761e39cc108f6c2559d934d 100644 --- a/e2e/protractor.conf.js +++ b/e2e/protractor.conf.js @@ -18,7 +18,7 @@ exports.config = { // Use headless chrome browserName: 'chrome', - chromeOptions: { + 'goog:chromeOptions': { args: [ ...chromeOpts ], diff --git a/e2e/src/advanced/browsingForDatasets.e2e-spec.js b/e2e/src/advanced/browsingForDatasets.e2e-spec.js new file mode 100644 index 0000000000000000000000000000000000000000..d6ec2e81070f009530a6d5461b8a96a6a948261d --- /dev/null +++ b/e2e/src/advanced/browsingForDatasets.e2e-spec.js @@ -0,0 +1,63 @@ +const { AtlasPage } = require('../util') + +const templates = [ + 'MNI Colin 27', + 'ICBM 2009c Nonlinear Asymmetric' +] + +const areasShouldHaveRecptor = [ + 'Area 7A (SPL)', + 'Area 3b (PostCG)', + 'Area PFm (IPL)', + 'Area PFop (IPL)', + 'Area PF (IPL)', + 'Area PGp (IPL)', + 'Area PGa (IPL)', + 'Area PFt (IPL)', + 'Area PFcm (IPL)', + 'Area hOc1 (V1, 17, CalcS)', + 'Area 44 (IFG)', + 'Area 45 (IFG)', + 'Area 4p (PreCG)', + 'Area TE 1.0 (HESCHL)', + 'Area FG1 (FusG)', + 'Area FG2 (FusG)' +] + +describe('dataset browser', () => { + let iavPage + beforeAll(async () => { + iavPage = new AtlasPage() + await iavPage.init() + }) + + for (const template of templates) { + describe(`in template: ${template}`, () => { + beforeAll(async () => { + await iavPage.goto() + await iavPage.selectTitleCard(template) + await iavPage.wait(500) + await iavPage.waitUntilAllChunksLoaded() + }) + + afterEach(async () => { + await iavPage.clearSearchRegionWithText() + await iavPage.clearAllSelectedRegions() + }) + for (const area of areasShouldHaveRecptor) { + it(`receptor data ${area} should be able to be found`, async () => { + await iavPage.searchRegionWithText(area) + await iavPage.wait(2000) + await iavPage.selectSearchRegionAutocompleteWithText() + await iavPage.dismissModal() + await iavPage.searchRegionWithText('') + + const datasets = await iavPage.getVisibleDatasets() + const filteredDs = datasets.filter(ds => ds.toLowerCase().indexOf('receptor') >= 0) + expect(filteredDs.length).toBeGreaterThan(0) + //TODO + }) + } + }) + } +}) \ No newline at end of file diff --git a/e2e/src/advanced/urlParsing.e2e-spec.js b/e2e/src/advanced/urlParsing.e2e-spec.js index 3608b8dd1c16b1b0bc41183a7a769de3d8ae4bd3..46b4bd13d8dbc85d0d161a3f4758d8f458f12ddb 100644 --- a/e2e/src/advanced/urlParsing.e2e-spec.js +++ b/e2e/src/advanced/urlParsing.e2e-spec.js @@ -47,7 +47,7 @@ describe('url parsing', () => { ] } - await iavPage.goto(searchParam) + await iavPage.goto(searchParam, { doNotAutomate: true }) await iavPage.wait(2000) const actualNav = await iavPage.getNavigationState() @@ -69,7 +69,7 @@ describe('url parsing', () => { searchParam.set('parcellationSelected', 'JuBrain Cytoarchitectonic Atlas') searchParam.set('pluginStates', 'http://localhost:3001/manifest.json') - await iavPage.goto(`/?${searchParam.toString()}`, { interceptHttp: true }) + await iavPage.goto(`/?${searchParam.toString()}`, { interceptHttp: true, doNotAutomate: true }) await iavPage.wait(10000) const interceptedCalls = await iavPage.getInterceptedHttpCalls() expect( diff --git a/e2e/src/iv.e2e-spec.js b/e2e/src/iv.e2e-spec.js deleted file mode 100644 index aac418a0d43619dfe18d78b1c1e1c21d1d32d739..0000000000000000000000000000000000000000 --- a/e2e/src/iv.e2e-spec.js +++ /dev/null @@ -1,125 +0,0 @@ -const chromeOpts = require('../chromeOpts') -const noErrorLog = require('./noErrorLog') -const { getSelectedTemplate, getSelectedParcellation, getSelectedRegions, getCurrentNavigationState, awaitNehubaViewer } = require('./ivApi') -const { getSearchParam, wait } = require('./util') -const { URLSearchParams } = require('url') - -const { waitMultiple } = require('./util') - -describe('protractor works', () => { - it('protractor works', () => { - expect(true).toEqual(true) - }) -}) - -const pptr = require('puppeteer') -const ATLAS_URL = (process.env.ATLAS_URL || 'http://localhost:3000').replace(/\/$/, '') -if (ATLAS_URL.length === 0) throw new Error(`ATLAS_URL must either be left unset or defined.`) -if (ATLAS_URL[ATLAS_URL.length - 1] === '/') throw new Error(`ATLAS_URL should not trail with a slash: ${ATLAS_URL}`) - -let browser -describe('IAV', () => { - beforeAll(async () => { - browser = await pptr.launch({ - ...( - chromeOpts.indexOf('--headless') >= 0 - ? { headless: true } - : {} - ), - args: [ - ...chromeOpts - ] - }) - }) - - // TODO figure out how to get jasmine to compare array members - describe('api', () => { - const urlMni152JuBrain = `${ATLAS_URL}/?templateSelected=MNI+152+ICBM+2009c+Nonlinear+Asymmetric&parcellationSelected=JuBrain+Cytoarchitectonic+Atlas&cRegionsSelected=%7B%22jubrain+mni152+v18+left%22%3A%222%22%2C%22jubrain+mni152+v18+right%22%3A%222%22%7D&cNavigation=0.0.0.-W000..2_ZG29.-ASCS.2-8jM2._aAY3..BSR0..70hl~.1w4W0~.70hk..1Pl9` - describe('selectRegion obs', () => { - it('should populate selected region with inherited properties', async () => { - const page = await browser.newPage() - await page.goto(urlMni152JuBrain, {waitUntil: 'networkidle2'}) - const regions = await getSelectedRegions(page) - for (const region of regions){ - expect(region.relatedAreas).toBeDefined() - expect( - region.relatedAreas.map(({ name }) => name).sort() - ).toEqual( - [ - 'Area 44v', - 'Area 44d' - ].sort() - ) - } - }) - }) - }) - - describe('Url parsing', () => { - - - it('pluginStates should result in call to fetch pluginManifest', async () => { - const searchParam = new URLSearchParams() - searchParam.set('templateSelected', 'MNI 152 ICBM 2009c Nonlinear Asymmetric') - searchParam.set('parcellationSelected', 'JuBrain Cytoarchitectonic Atlas') - searchParam.set('pluginStates', 'http://localhost:3001/manifest.json') - - const page = await browser.newPage() - - await page.setRequestInterception(true) - - const externalApi = { - manifestCalled: false, - templateCalled: false, - scriptCalled: false - } - - page.on('request', async req => { - const url = await req.url() - switch (url) { - case 'http://localhost:3001/manifest.json': { - externalApi.manifestCalled = true - req.respond({ - content: 'application/json', - headers: { 'Access-Control-Allow-Origin': '*' }, - body: JSON.stringify({ - name: 'test plugin', - templateURL: 'http://localhost:3001/template.html', - scriptURL: 'http://localhost:3001/script.js' - }) - }) - break; - } - case 'http://localhost:3001/template.html': { - externalApi.templateCalled = true - req.respond({ - content: 'text/html; charset=UTF-8', - headers: { 'Access-Control-Allow-Origin': '*' }, - body: '' - }) - break; - } - case 'http://localhost:3001/script.js': { - externalApi.scriptCalled = true - req.respond({ - content: 'application/javascript', - headers: { 'Access-Control-Allow-Origin': '*' }, - body: '' - }) - break; - } - default: req.continue() - } - }) - - await page.goto(`${ATLAS_URL}/?${searchParam.toString()}`, { waitUntil: 'networkidle2' }) - // await awaitNehubaViewer(page) - await page.waitFor(500 * waitMultiple) - - expect(externalApi.manifestCalled).toBe(true) - expect(externalApi.templateCalled).toBe(true) - expect(externalApi.scriptCalled).toBe(true) - - }) - }) -}) diff --git a/e2e/src/ivApi.js b/e2e/src/ivApi.js deleted file mode 100644 index 83d5a48a81f9195f7c9d98c61f6be37bc487b694..0000000000000000000000000000000000000000 --- a/e2e/src/ivApi.js +++ /dev/null @@ -1,66 +0,0 @@ -const { retry } = require('../../common/util') -const { waitMultiple } = require('./util') - -exports.getSelectedTemplate = (browser) => new Promise((resolve, reject) => { - browser.executeAsyncScript('let sub = window.interactiveViewer.metadata.selectedTemplateBSubject.subscribe(obj => arguments[arguments.length - 1](obj));sub.unsubscribe()') - .then(resolve) - .catch(reject) -}) - -exports.getSelectedParcellation = (browser) => new Promise((resolve, reject) => { - browser.executeAsyncScript('let sub = window.interactiveViewer.metadata.selectedParcellationBSubject.subscribe(obj => arguments[arguments.length - 1](obj));sub.unsubscribe()') - .then(resolve) - .catch(reject) -}) - -exports.getSelectedRegions = async (page) => { - return await page.evaluate(async () => { - let region, sub - const getRegion = () => new Promise(rs => { - sub = interactiveViewer.metadata.selectedRegionsBSubject.subscribe(rs) - }) - - region = await getRegion() - sub.unsubscribe() - return region - }) -} - -exports.getCurrentNavigationState = page => new Promise(async rs => { - const rObj = await page.evaluate(async () => { - - let returnObj, sub - const getPr = () => new Promise(rs => { - - sub = nehubaViewer.navigationState.all - .subscribe(({ orientation, perspectiveOrientation, perspectiveZoom, position, zoom }) => { - returnObj = { - orientation: Array.from(orientation), - perspectiveOrientation: Array.from(perspectiveOrientation), - perspectiveZoom, - zoom, - position: Array.from(position) - } - rs() - }) - }) - - await getPr() - sub.unsubscribe() - - return returnObj - }) - rs(rObj) -}) - -exports.awaitNehubaViewer = async (page) => { - const getNVAvailable = () => new Promise(async (rs, rj) => { - const result = await page.evaluate(() => { - return !!window.nehubaViewer - }) - if (result) return rs() - else return rj() - }) - - await retry(getNVAvailable, { timeout: 2000 * waitMultiple, retries: 10 }) -} diff --git a/e2e/src/navigating/mouseOverNehuba.e2e-spec.js b/e2e/src/navigating/mouseOverNehuba.e2e-spec.js new file mode 100644 index 0000000000000000000000000000000000000000..593246a03df98cbed08abb6a00d3a182115a26f2 --- /dev/null +++ b/e2e/src/navigating/mouseOverNehuba.e2e-spec.js @@ -0,0 +1,189 @@ +const { AtlasPage } = require('../util') + +const dictionary = { + "Allen Mouse Common Coordinate Framework v3": { + "Allen Mouse Common Coordinate Framework v3 2017": { + tests: [ + { + position: [530, 530], + expectedLabelName: 'Primary somatosensory area, barrel field, layer 4', + }, + { + position: [590, 120], + expectedLabelName: 'Retrosplenial area, ventral part, layer 2/3', + } + ] + }, + "Allen Mouse Common Coordinate Framework v3 2015": { + tests: [ + { + position: [520, 530], + expectedLabelName: 'Primary somatosensory area, barrel field', + }, + { + position: [588, 115], + expectedLabelName: 'Retrosplenial area, ventral part', + } + ] + } + }, + "Waxholm Space rat brain MRI/DTI": { + "Waxholm Space rat brain atlas v3": { + tests: [ + { + position: [350, 170], + expectedLabelName: 'neocortex', + }, + { + position: [320, 560], + expectedLabelName: 'corpus callosum and associated subcortical white matter', + } + ] + }, + "Waxholm Space rat brain atlas v2": { + tests: [ + { + position: [500, 630], + expectedLabelName: 'lateral entorhinal cortex', + }, + { + position: [300, 200], + expectedLabelName: 'dentate gyrus', + } + ] + }, + "Waxholm Space rat brain atlas v1": { + tests: [ + { + position: [480, 680], + expectedLabelName: 'inner ear', + }, + { + position: [550, 550], + expectedLabelName: 'corpus callosum and associated subcortical white matter', + } + ] + } + }, + "ICBM 2009c Nonlinear Asymmetric": { + "JuBrain Cytoarchitectonic Atlas": { + tests:[ + { + position: [550, 270], + expectedLabelName: 'Fastigial Nucleus (Cerebellum) - right hemisphere', + }, + { + position: [600, 490], + expectedLabelName: 'Area 6ma (preSMA, mesial SFG) - left hemisphere', + } + ] + }, + "Fibre Bundle Atlas - Long Bundle": { + tests: [ + { + position: [300, 210], + expectedLabelName: 'InferiorFrontoOccipital - Right', + }, + { + position: [680, 590], + expectedLabelName: 'InferiorLongitudinal - Left', + } + ] + }, + "Fibre Bundle Atlas - Short Bundle": { + tests: [ + { + position: [300, 100], + expectedLabelName: 'rh_SP-SM_0', + }, + { + position: [642, 541], + expectedLabelName: 'lh_PoCi-PrCu_0', + } + ] + } + }, + // TODO big brain cytomap occassionally resets center position to -20mm. investigate why + "Big Brain (Histology)": { + "Cytoarchitectonic Maps": { + url: "/?templateSelected=Big+Brain+%28Histology%29&parcellationSelected=Cytoarchitectonic+Maps&cNavigation=0.0.0.-W000.._eCwg.2-FUe3._-s_W.2_evlu..7LIx..1n5q~.1FYC.2Is-..1B9C", + tests: [ + { + position: [686, 677], + expectedLabelName: 'Area STS1 (STS)' + }, + { + position: [617,682], + expectedLabelName: 'Entorhinal Cortex' + } + ] + }, + "BigBrain Cortical Layers Segmentation": { + url: "/?templateSelected=Big+Brain+%28Histology%29&parcellationSelected=BigBrain+Cortical+Layers+Segmentation&cNavigation=0.0.0.-W000.._eCwg.2-FUe3._-s_W.2_evlu..7LIx..2c8U8.1FYC.wfmi..91G", + tests: [ + { + position: [330, 550], + expectedLabelName: 'cortical layer 6', + }, + { + position: [475, 244], + expectedLabelName: 'cortical layer 1', + } + ] + }, + "Grey/White matter": { + url: "/?templateSelected=Big+Brain+%28Histology%29&parcellationSelected=Grey%2FWhite+matter&cNavigation=0.0.0.-W000.._eCwg.2-FUe3._-s_W.2_evlu..7LIx..3cX1m.1FYC.Cr8t..5Jn", + tests: [ + { + position: [210, 238], + expectedLabelName: 'White matter' + }, + { + position: [293, 598], + expectedLabelName: 'Grey matter' + } + ] + } + } +} + +describe('mouse over viewer show area name', () => { + let iavPage + beforeAll(async () => { + iavPage = new AtlasPage() + await iavPage.init() + }) + + for (const templateName in dictionary) { + for (const parcellationName in dictionary[templateName]) { + describe(`testing template: ${templateName} & parcellation: ${parcellationName}`, () => { + + const {url, tests} = dictionary[templateName][parcellationName] + beforeAll(async () => { + if (url) { + await iavPage.goto(url) + } else { + await iavPage.goto() + await iavPage.selectTitleTemplateParcellation(templateName, parcellationName) + } + + const tag = await iavPage.getSideNavTag() + await tag.click() + await iavPage.wait(1000) + await iavPage.waitUntilAllChunksLoaded() + }) + for (const { position, expectedLabelName } of tests ) { + it(`at cursor position: ${JSON.stringify(position)}, expect label name: ${expectedLabelName}`, async () => { + + await iavPage.cursorMoveTo({ position }) + await iavPage.wait(2000) + const text = await iavPage.getFloatingCtxInfoAsText() + expect( + text.indexOf(expectedLabelName) + ).toBeGreaterThanOrEqual(0) + }) + } + }) + } + } +}) diff --git a/e2e/src/noErrorLog.js b/e2e/src/noErrorLog.js deleted file mode 100644 index d7bc1ef7dc223d3f66aece5994623fbaea239823..0000000000000000000000000000000000000000 --- a/e2e/src/noErrorLog.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = (browser) => { - if (!browser) { - fail('browser not defined') - throw new Error('browser not defined') - } - browser.manage().logs().get('browser') - .then(browserLogs => { - const errorLogs = browserLogs - .filter(log => !(/favicon/.test(log.message))) - .filter(log => log.level.value > 900) - expect(errorLogs.length).toEqual(0) - }) - .catch(fail) -} \ No newline at end of file diff --git a/e2e/src/selecting/template.e2e-spec.js b/e2e/src/selecting/template.e2e-spec.js index 31241bead68deb923332df633389d214e1c4df82..433571e3f2b1417f260ba25af65616c1d44ebd1f 100644 --- a/e2e/src/selecting/template.e2e-spec.js +++ b/e2e/src/selecting/template.e2e-spec.js @@ -10,10 +10,6 @@ describe('selecting template', () => { it('can select template by clicking main card', async () => { await iavPage.goto() - await iavPage.wait(200) - await iavPage.dismissModal() - await iavPage.wait(200) - await iavPage.selectTitleCard('ICBM 2009c Nonlinear Asymmetric') await iavPage.wait(1000) @@ -25,9 +21,6 @@ describe('selecting template', () => { it('switching template after template init by clicking select should work', async () => { await iavPage.goto() - await iavPage.wait(200) - await iavPage.dismissModal() - await iavPage.wait(200) await iavPage.selectTitleCard('ICBM 2009c Nonlinear Asymmetric') await iavPage.wait(1000) diff --git a/e2e/src/util.js b/e2e/src/util.js index d7a147e765a9b9dab7ef3529b81b1ed606708e08..b43524d943ba41018329e023a37d55450f0d7f5a 100644 --- a/e2e/src/util.js +++ b/e2e/src/util.js @@ -4,7 +4,7 @@ const ATLAS_URL = (process.env.ATLAS_URL || 'http://localhost:3000').replace(/\/ const USE_SELENIUM = !!process.env.SELENIUM_ADDRESS if (ATLAS_URL.length === 0) throw new Error(`ATLAS_URL must either be left unset or defined.`) if (ATLAS_URL[ATLAS_URL.length - 1] === '/') throw new Error(`ATLAS_URL should not trail with a slash: ${ATLAS_URL}`) -const { By, WebDriver } = require('selenium-webdriver') +const { By, WebDriver, Key } = require('selenium-webdriver') function getActualUrl(url) { return /^http\:\/\//.test(url) ? url : `${ATLAS_URL}/${url.replace(/^\//, '')}` @@ -17,15 +17,52 @@ async function getIndexFromArrayOfWebElements(search, webElements) { return texts.findIndex(text => text.indexOf(search) >= 0) } +const regionSearchAriaLabelText = 'Search for any region of interest in the atlas selected' + class WdBase{ constructor() { browser.waitForAngularEnabled(false) } get browser(){ - return this.driver || browser + return browser + } + get driver(){ + return this.browser.driver } async init() { + const wSizeArg = chromeOpts.find(arg => arg.indexOf('--window-size') >= 0) + const [ _, width, height ] = /\=([0-9]{1,})\,([0-9]{1,})$/.exec(wSizeArg) + + const newDim = await this.browser.executeScript(async () => { + return [ + window.outerWidth - window.innerWidth + (+ arguments[0]), + window.outerHeight - window.innerHeight + (+ arguments[1]), + ] + }, width, height) + + await this.browser.manage() + .window() + .setRect({ + width: newDim[0], + height: newDim[1] + }) + } + async cursorMoveTo({ position }) { + if (!position) throw new Error(`cursorGoto: position must be defined!`) + const x = Array.isArray(position) ? position[0] : position.x + const y = Array.isArray(position)? position[1] : position.y + if (!x) throw new Error(`cursorGoto: position.x or position[0] must be defined`) + if (!y) throw new Error(`cursorGoto: position.y or position[1] must be defined`) + + return this.driver.actions() + .move() + .move({ + x, + y, + duration: 1000 + }) + .perform() } async initHttpInterceptor(){ @@ -55,7 +92,7 @@ class WdBase{ return window['__interceptedXhr__'] }) } - async goto(url = '/', { interceptHttp } = {}){ + async goto(url = '/', { interceptHttp, doNotAutomate } = {}){ const actualUrl = getActualUrl(url) if (interceptHttp) { this.browser.get(actualUrl) @@ -63,7 +100,14 @@ class WdBase{ } else { await this.browser.get(actualUrl) } + + if (!doNotAutomate) { + await this.wait(200) + await this.dismissModal() + await this.wait(200) + } } + async wait(ms) { if (!ms) throw new Error(`wait duration must be specified!`) await this.browser.sleep(ms) @@ -102,13 +146,28 @@ class WdLayoutPage extends WdBase{ .findElements( By.tagName('mat-card') ) } - async selectTitleCard( title ) { + async findTitleCard(title) { const titleCards = await this.findTitleCards() const idx = await getIndexFromArrayOfWebElements(title, titleCards) - if (idx >= 0) await titleCards[idx].click() + if (idx >= 0) return titleCards[idx] else throw new Error(`${title} does not fit any titleCards`) } + async selectTitleCard( title ) { + const titleCard = await this.findTitleCard(title) + await titleCard.click() + } + + async selectTitleTemplateParcellation(templateName, parcellationName){ + const titleCard = await this.findTitleCard(templateName) + const parcellations = await titleCard + .findElement( By.css('mat-card-content.available-parcellations-container') ) + .findElements( By.tagName('button') ) + const idx = await getIndexFromArrayOfWebElements( parcellationName, parcellations ) + if (idx >= 0) await parcellations[idx].click() + else throw new Error(`parcellationName ${parcellationName} does not exist`) + } + async getSideNav() { return await this.browser.findElement( By.tagName('search-side-nav') ) } @@ -129,6 +188,39 @@ class WdIavPage extends WdLayoutPage{ super() } + async clearAllSelectedRegions() { + const clearAllRegionBtn = await this.browser.findElement( + By.css('[aria-label="Clear all regions"]') + ) + await clearAllRegionBtn.click() + await this.wait(500) + } + + async waitUntilAllChunksLoaded(){ + const checkReady = async () => { + const el = await this.browser.findElements( + By.css('div.loadingIndicator') + ) + return !el.length + } + + do { + // Do nothing, until ready + } while ( + await this.wait(1000), + !(await checkReady()) + ) + } + + async getFloatingCtxInfoAsText(){ + const floatingContainer = await this.browser.findElement( + By.css('div[floatingMouseContextualContainerDirective]') + ) + + const text = await floatingContainer.getText() + return text + } + async selectDropdownTemplate(title) { const templateBtn = await (await this.getSideNav()) .findElement( By.tagName('viewer-state-controller') ) @@ -143,14 +235,66 @@ class WdIavPage extends WdLayoutPage{ else throw new Error(`${title} is not found as one of the dropdown templates`) } - async getViewerContainer() { - return await this.browser.findElement( - By.id('neuroglancer-container') - ) + async getSearchRegionInput(){ + return await (await this.getSideNav()) + .findElement( By.css(`[aria-label="${regionSearchAriaLabelText}"]`) ) + } + + async searchRegionWithText(text=''){ + const searchRegionInput = await this.getSearchRegionInput() + await searchRegionInput + .sendKeys( + Key.chord(Key.CONTROL, 'a'), + text + ) + } + + async clearSearchRegionWithText() { + const searchRegionInput = await this.getSearchRegionInput() + await searchRegionInput + .sendKeys( + Key.chord(Key.CONTROL, 'a'), + Key.BACK_SPACE, + Key.ESCAPE + ) + } + + async getSearchRegionInputAutoCompleteOptions(){ + } + + async selectSearchRegionAutocompleteWithText(text = ''){ + + const input = await this.getSearchRegionInput() + const autocompleteId = await input.getAttribute('aria-owns') + const options = await this.browser + .findElement( By.id( autocompleteId ) ) + .findElements( By.tagName('mat-option') ) + + const idx = await getIndexFromArrayOfWebElements(text, options) + if (idx >= 0) { + await options[idx].click() + } else { + throw new Error(`getIndexFromArrayOfWebElements ${text} option not founds`) + } + } + + async getVisibleDatasets() { + const singleDatasetListView = await this.browser + .findElement( By.tagName('data-browser') ) + .findElement( By.css('div.cdk-virtual-scroll-content-wrapper') ) + .findElements( By.tagName('single-dataset-list-view') ) + + const returnArr = [] + for (const item of singleDatasetListView) { + returnArr.push( await item.getText() ) + } + return returnArr } async viewerIsPopulated() { - const ngContainer = await this.getViewerContainer() + const ngContainer = await this.browser.findElement( + By.id('neuroglancer-container') + ) const canvas = await ngContainer.findElement( By.tagName('canvas') ) diff --git a/package.json b/package.json index a2b59d27eb77ed6feeab5616f03f8127b38e1043..69cd4628c3087ae7e0a45c66171ff9fa6f8f43f5 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "lodash.merge": "^4.6.2", "mini-css-extract-plugin": "^0.8.0", "node-sass": "^4.12.0", - "protractor": "^5.4.2", + "protractor": "^6.0.0", "raw-loader": "^0.5.1", "reflect-metadata": "^0.1.12", "rxjs": "6.5.4", diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.template.html b/src/ui/nehubaContainer/splashScreen/splashScreen.template.html index 90dfb6b6cba80c3c4979146bd7cef5826d7af42d..9d4a9b4ce15e5d02305353964bb1595d1fc598d1 100644 --- a/src/ui/nehubaContainer/splashScreen/splashScreen.template.html +++ b/src/ui/nehubaContainer/splashScreen/splashScreen.template.html @@ -24,7 +24,7 @@ {{ template.properties.description }} </mat-card-content> - <mat-card-content> + <mat-card-content class="available-parcellations-container"> <mat-card-subtitle class="text-nowrap"> Parcellations available </mat-card-subtitle> diff --git a/src/ui/searchSideNav/searchSideNav.template.html b/src/ui/searchSideNav/searchSideNav.template.html index d504105a6a72b6ff19ec6dcb48fff8d778529fde..195f2e5d5c3bb4036f3e9eef72cd624e62d97270 100644 --- a/src/ui/searchSideNav/searchSideNav.template.html +++ b/src/ui/searchSideNav/searchSideNav.template.html @@ -117,6 +117,7 @@ class="position-absolute" (click)="deselectAllRegions()" matTooltip="Clear all regions" + aria-label="Clear all regions" color="primary"> <i class="fas fa-times-circle"></i> </button> diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.component.ts b/src/ui/viewerStateController/regionSearch/regionSearch.component.ts index 0b013fd3c6a2f82a9c1fb6f913c0a45a002e62fe..01cdf6d3745179591209349a536e84073d79944c 100644 --- a/src/ui/viewerStateController/regionSearch/regionSearch.component.ts +++ b/src/ui/viewerStateController/regionSearch/regionSearch.component.ts @@ -30,6 +30,7 @@ export class RegionTextSearchAutocomplete { public compareFn = compareFn + @Input() public ariaLabel: string = `Search for any region of interest in the atlas selected` @Input() public showBadge: boolean = false @Input() public showAutoComplete: boolean = true diff --git a/src/ui/viewerStateController/regionSearch/regionSearch.template.html b/src/ui/viewerStateController/regionSearch/regionSearch.template.html index db6ec9747c3573769c1a3dfbaf36869a3694cc7e..25c2cf5fec6f1bf275c251344d6d6fccfa9ef089 100644 --- a/src/ui/viewerStateController/regionSearch/regionSearch.template.html +++ b/src/ui/viewerStateController/regionSearch/regionSearch.template.html @@ -8,6 +8,7 @@ #trigger="matAutocompleteTrigger" type="text" matInput + [attr.aria-label]="ariaLabel" [formControl]="formControl" [matAutocomplete]="auto"> </mat-form-field> diff --git a/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html b/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html index 3da00c0c9a7475f7107fc717feb2ab5f35d6cf2f..2e11b8d7db09d47acfcf857f90fd244e44e59483 100644 --- a/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html +++ b/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html @@ -31,6 +31,7 @@ <!-- shown when off --> <div sleight-of-hand-front> <button + aria-label="Hover to find out more info on the selected template" mat-icon-button> <i class="fas fa-info"></i> </button> @@ -38,7 +39,10 @@ <!-- shown on hover --> <div class="d-flex flex-row align-items-start" sleight-of-hand-back> - <button class="flex-grow-0 flex-shrink-0" mat-icon-button> + <button + aria-label="Hover to find out more info on the selected template" + class="flex-grow-0 flex-shrink-0" + mat-icon-button> <i class="fas fa-info"></i> </button>