diff --git a/e2e/chromeOpts.js b/e2e/chromeOpts.js
index f5447a0912e3ef936136ed0cbc91b17ba00b3f2e..7e4076611c1484a12ce26fd9b26f5e2246d1920d 100644
--- a/e2e/chromeOpts.js
+++ b/e2e/chromeOpts.js
@@ -3,7 +3,7 @@ const { width, height } = require('./opts')
 module.exports = [
   ...(process.env.DISABLE_CHROME_HEADLESS ?  [] : ['--headless']),
   '--no-sandbox',
-  '--disable-gpu',
+  ...(process.env.ENABLE_GPU ? []: ['--disable-gpu']),
   '--disable-setuid-sandbox',
   "--disable-extensions",
   `--window-size=${width},${height}`,
diff --git a/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js b/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js
index 87099a58e463dedc82b7c57680badd71f30013c5..beb15ab520ef7820ada5ecfc0e676db7c36babad 100644
--- a/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js
+++ b/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js
@@ -1,7 +1,6 @@
 const { AtlasPage } = require('../util')
-const { ARIA_LABELS } = require('../../../common/constants')
+const { CONST } = require('../../../common/constants')
 const { retry } = require('../../../common/util')
-const { TOGGLE_EXPLORE_PANEL, MODALITY_FILTER, DOWNLOAD_PREVIEW, DOWNLOAD_PREVIEW_CSV } = ARIA_LABELS
 
 const atlasName = `Multilevel Human Atlas`
 
@@ -110,7 +109,7 @@ describe('> dataset browser', () => {
           await iavPage.selectSearchRegionAutocompleteWithText()
           await retry(async () => {
             await iavPage.dismissModal()
-            await iavPage._setRegionalFeaturesExpanded(true)
+            await iavPage.toggleExpansionPanelState(`${CONST.REGIONAL_FEATURES}`)
           }, {
             timeout: 2000,
             retries: 10
@@ -126,153 +125,3 @@ describe('> dataset browser', () => {
     })
   }
 })
-
-const template = 'ICBM 2009c Nonlinear Asymmetric'
-const area = 'Area hOc1 (V1, 17, CalcS)'
-
-const receptorName = `Density measurements of different receptors for Area hOc1 (V1, 17, CalcS) [human, v1.0]`
-
-// describe('> receptor dataset previews', () => {
-//   let iavPage
-//   beforeEach(async () => {
-//     iavPage = new AtlasPage()
-//     await iavPage.init()
-//     await iavPage.goto()
-//     await iavPage.selectTitleCard(template)
-//     await iavPage.wait(500)
-//     await iavPage.waitUntilAllChunksLoaded()
-
-//     await iavPage.searchRegionWithText(area)
-//     await iavPage.wait(2000)
-//     await iavPage.selectSearchRegionAutocompleteWithText()
-//     await iavPage.dismissModal()
-//     await iavPage.searchRegionWithText('')
-
-//     const datasets = await iavPage.getVisibleDatasets()
-//     const receptorIndex = datasets.indexOf(receptorName)
-
-//     await iavPage.clickNthDataset(receptorIndex)
-//     await iavPage.wait(500)
-//     await iavPage.click(`[aria-label="${ARIA_LABELS.SHOW_DATASET_PREVIEW}"]`)
-//     await iavPage.waitFor(true, true)
-//   })
-
-//   describe('> can display graph', () => {
-
-//     it('> can display radar graph', async () => {
-//       const files = await iavPage.getBottomSheetList()
-//       const fingerprintIndex = files.findIndex(file => /fingerprint/i.test(file))
-//       await iavPage.clickNthItemFromBottomSheetList(fingerprintIndex)
-//       await iavPage.waitFor(true, true)
-//       const modalHasCanvas = await iavPage.modalHasChild('canvas')
-//       expect(modalHasCanvas).toEqual(true)
-
-//       await iavPage.wait(500)
-
-//       const modalHasDownloadBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW}"]`)
-//       const modalHasDownloadCSVBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW_CSV}"]`)
-
-//       expect(modalHasDownloadBtn).toEqual(true)
-//       expect(modalHasDownloadCSVBtn).toEqual(true)
-//     })
-
-//     it('> can display profile', async () => {
-
-//       const files = await iavPage.getBottomSheetList()
-//       const profileIndex = files.findIndex(file => /profile/i.test(file))
-//       await iavPage.clickNthItemFromBottomSheetList(profileIndex)
-//       await iavPage.waitFor(true, true)
-//       const modalHasCanvas = await iavPage.modalHasChild('canvas')
-//       expect(modalHasCanvas).toEqual(true)
-
-//       await iavPage.wait(500)
-
-//       const modalHasDownloadBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW}"]`)
-//       const modalHasDownloadCSVBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW_CSV}"]`)
-
-//       expect(modalHasDownloadBtn).toEqual(true)
-//       expect(modalHasDownloadCSVBtn).toEqual(true)
-//     })
-//   })
-//   it('> can display image', async () => {
-//     const files = await iavPage.getBottomSheetList()
-//     const imageIndex = files.findIndex(file => /image\//i.test(file))
-//     await iavPage.clickNthItemFromBottomSheetList(imageIndex)
-//     await iavPage.wait(500)
-//     const modalHasImage = await iavPage.modalHasChild('div[data-img-src]')
-//     expect(modalHasImage).toEqual(true)
-
-//     await iavPage.wait(500)
-
-//     const modalHasDownloadBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW}"]`)
-//     const modalHasDownloadCSVBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW_CSV}"]`)
-
-//     expect(modalHasDownloadBtn).toEqual(true)
-//     expect(modalHasDownloadCSVBtn).toEqual(false)
-//   })
-// })
-
-// describe('> modality picker', () => {
-//   let iavPage
-//   beforeAll(async () => {
-//     iavPage = new AtlasPage()
-//     await iavPage.init()
-//     await iavPage.goto()
-//   })
-//   it('> sorted alphabetically', async () => {
-//     await iavPage.selectTitleCard(templates[1])
-//     await iavPage.wait(500)
-//     await iavPage.waitUntilAllChunksLoaded()
-//     await iavPage.click(`[aria-label="${TOGGLE_EXPLORE_PANEL}"]`)
-//     await iavPage.wait(500)
-//     await iavPage.clearAlerts()
-//     await iavPage.click(`[aria-label="${MODALITY_FILTER}"]`)
-//     await iavPage.wait(500)
-//     const modalities = await iavPage.getModalities()
-//     for (let i = 1; i < modalities.length; i ++) {
-//       expect(
-//         modalities[i].charCodeAt(0)
-//       ).toBeGreaterThanOrEqual(
-//         modalities[i - 1].charCodeAt(0)
-//       )
-//     }
-//   })
-// })
-
-
-// describe('> pmap dataset preview', () => {
-//   let iavPage
-
-//   beforeAll(async () => {
-//     // loads pmap and centers on hot spot
-//     const url = `/?templateSelected=MNI+152+ICBM+2009c+Nonlinear+Asymmetric&parcellationSelected=JuBrain+Cytoarchitectonic+Atlas&cNavigation=0.0.0.-W000..2_ZG29.-ASCS.2-8jM2._aAY3..BSR0..dABI~.525x0~.7iMV..1EPC&niftiLayers=https%3A%2F%2Fneuroglancer.humanbrainproject.eu%2Fprecomputed%2FJuBrain%2F17%2Ficbm152casym%2Fpmaps%2FVisual_hOc1_l_N10_nlin2MNI152ASYM2009C_2.4_publicP_d3045ee3c0c4de9820eb1516d2cc72bb.nii.gz&previewingDatasetFiles=%5B%7B"datasetId"%3A"minds%2Fcore%2Fdataset%2Fv1.0.0%2F5c669b77-c981-424a-858d-fe9f527dbc07"%2C"filename"%3A"Area+hOc1+%28V1%2C+17%2C+CalcS%29+%5Bv2.4%2C+ICBM+2009c+Asymmetric%2C+left+hemisphere%5D"%7D%5D`
-//     iavPage = new AtlasPage()
-//     await iavPage.init()
-//     await iavPage.goto(url)
-//     await iavPage.waitUntilAllChunksLoaded()
-//   })
-
-//   it('> can display pmap', async () => {
-//     const { red, green, blue } = await iavPage.getRgbAt({position: [200, 597]})
-//     expect(red).toBeGreaterThan(green)
-//     expect(red).toBeGreaterThan(blue)
-//   })
-
-//   it('> on update of layer control, pmap retains', async () => {
-//     // by default, additional layer control is collapsed
-//     // await iavPage.toggleLayerControl() // deprecated
-//     await iavPage.wait(500)
-//     await iavPage.toggleNthLayerControl(0)
-//     await iavPage.wait(5500)
-
-//     // interact with control
-//     await iavPage.click(`[aria-label="Remove background"]`)
-//     await iavPage.wait(500)
-
-//     // color map should be unchanged
-//     const { red, green, blue } = await iavPage.getRgbAt({position: [200, 597]})
-//     expect(red).toBeGreaterThan(green)
-//     expect(red).toBeGreaterThan(blue)
-    
-//   })
-// })
diff --git a/e2e/src/advanced/nonAtlasImages.prod.e2e-spec.js b/e2e/src/advanced/nonAtlasImages.prod.e2e-spec.js
index d9e46c2364b528cbe9c5d44000861c63a766bab2..3277e69e5a4328233addc63260b9fd06e876eb5b 100644
--- a/e2e/src/advanced/nonAtlasImages.prod.e2e-spec.js
+++ b/e2e/src/advanced/nonAtlasImages.prod.e2e-spec.js
@@ -1,5 +1,6 @@
 const { AtlasPage } = require('../util')
 const { URLSearchParams } = require('url')
+const { ARIA_LABELS } = require('../../../common/constants')
 describe('> non-atlas images', () => {
   let iavPage
 
@@ -196,9 +197,15 @@ describe('> non-atlas images', () => {
       await iavPage.goto(`/?${searchParam.toString()}`)
       await iavPage.wait(2000)
 
-      const additionalLayerControlIsShown = await iavPage.additionalLayerControlIsVisible()
-
-      expect(additionalLayerControlIsShown).toEqual(false)
+      try {
+        const additionalLayerControlIsShown = await iavPage.isVisible(`[aria-label="${ARIA_LABELS.ADDITIONAL_VOLUME_CONTROL}"]`)
+        expect(additionalLayerControlIsShown).toEqual(false)
+      } catch (e) {
+        /**
+         * error when css querying additional volume control
+         * expected behaviour, when it is not visible
+         */
+      }
       
     })
 
@@ -219,7 +226,7 @@ describe('> non-atlas images', () => {
       await iavPage.goto(`/?${searchParam.toString()}`, { forceTimeout: 20000 })
       await iavPage.wait(2000)
       
-      const additionalLayerCtrlIsExpanded2 = await iavPage.additionalLayerControlIsExpanded()
+      const additionalLayerCtrlIsExpanded2 = await iavPage.isVisible(`[aria-label="${ARIA_LABELS.ADDITIONAL_VOLUME_CONTROL}"]`)
       expect(additionalLayerCtrlIsExpanded2).toEqual(true)
 
     })
diff --git a/e2e/src/navigating/originDataset.prod.e2e-spec.js b/e2e/src/navigating/originDataset.prod.e2e-spec.js
index 0dbd2282f0031a91db584f90a8085e2953b09719..ff4aea1de27ca8338f393a8c9e0f1e560272d2ed 100644
--- a/e2e/src/navigating/originDataset.prod.e2e-spec.js
+++ b/e2e/src/navigating/originDataset.prod.e2e-spec.js
@@ -67,7 +67,7 @@ describe('origin dataset pmap', () => {
             await iavPage.wait(5000)
             await iavPage.waitForAsync()
 
-            const additionalLayerControlIsShown = await iavPage.additionalLayerControlIsVisible()
+            const additionalLayerControlIsShown = await iavPage.isVisible(`[aria-label="${ARIA_LABELS.ADDITIONAL_VOLUME_CONTROL}"]`)
             expect(additionalLayerControlIsShown).toEqual(false)
             
             const checked = await iavPage.switchIsChecked(cssSelector)
diff --git a/e2e/src/util.js b/e2e/src/util.js
index 26d9e739bcb6e256ee0b97b30e2d6dd6230fa349..2b8ada185465a382874c25a8009da43c710fea8f 100644
--- a/e2e/src/util.js
+++ b/e2e/src/util.js
@@ -1,1130 +1,9 @@
-const chromeOpts = require('../chromeOpts')
-// const pptr = require('puppeteer')
-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, Key, until } = require('selenium-webdriver')
-const CITRUS_LIGHT_URL = `https://unpkg.com/citruslight@0.1.0/citruslight.js`
-const { polyFillClick } = require('./material-util')
 
-const { ARIA_LABELS, CONST } = require('../../common/constants')
-const { retry } = require('../../common/util')
+const {
+  BasePage,
+  AtlasPage,
+  LayoutPage,
+} = require('../util/helper')
 
-function getActualUrl(url) {
-  return /^http\:\/\//.test(url) ? url : `${ATLAS_URL}/${url.replace(/^\//, '')}`
-}
-
-function _getTextFromWebElement(webElement) {
-  return webElement.getText()
-}
-
-async function _getIndexFromArrayOfWebElements(search, webElements) {
-  const texts = await Promise.all(
-    webElements.map(_getTextFromWebElement)
-  )
-  return texts.findIndex(text => text.indexOf(search) >= 0)
-}
-
-const verifyPosition = 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 {
-    x,
-    y
-  }
-}
-
-class WdBase{
-  constructor() {
-    browser.waitForAngularEnabled(false)
-  }
-  get _browser(){
-    return browser
-  }
-  get _driver(){
-    return this._browser.driver
-  }
-
-  // without image header
-  // output as b64 png
-  async takeScreenshot(cssSelector){
-    
-    if(cssSelector) {
-      await this._browser.executeAsyncScript(async () => {
-        const cb = arguments[arguments.length - 1]
-        const moduleUrl = arguments[0]
-        const cssSelector = arguments[1]
-
-        const el = document.querySelector(cssSelector)
-        if (!el) throw new Error(`css selector not fetching anything`)
-        import(moduleUrl)
-          .then(async m => {
-            m.citruslight(el)
-            cb()
-          })
-      }, CITRUS_LIGHT_URL, cssSelector)
-    }
-    
-    await this.wait(1000)
-    const result = await this._browser.takeScreenshot()
-
-    if (cssSelector) {
-      await this._browser.executeAsyncScript(async () => {
-        const cb = arguments[arguments.length - 1]
-        const moduleUrl = arguments[0]
-        const cssSelector = arguments[1]
-
-        const el = document.querySelector(cssSelector)
-        if (!el) throw new Error(`css selector not fetching anything`)
-        import(moduleUrl)
-          .then(async m => {
-            m.clearAll()
-            cb()
-          })
-      }, CITRUS_LIGHT_URL, cssSelector)
-    }
-    
-    await this.wait(1000)
-    return result
-  }
-
-  async getRgbAt({ position } = {}, cssSelector = null){
-    if (!position) throw new Error(`position is required for getRgbAt`)
-    const { x, y } = verifyPosition(position)
-    const screenshotData = await this.takeScreenshot(cssSelector)
-    const [ red, green, blue ] = await this._driver.executeAsyncScript(() => {
-      
-      const dataUri = arguments[0]
-      const pos = arguments[1]
-      const dim = arguments[2]
-      const cb = arguments[arguments.length - 1]
-
-      const img = new Image()
-      img.onload = () => {
-        const canvas = document.createElement('canvas')
-        canvas.width = dim[0]
-        canvas.height = dim[1]
-
-        const ctx = canvas.getContext('2d')
-        ctx.drawImage(img, 0, 0)
-        const imgData = ctx.getImageData(0, 0, dim[0], dim[1])
-
-        const idx = (dim[0] * pos[1] + pos[0]) * 4
-        const red = imgData.data[idx]
-        const green = imgData.data[idx + 1]
-        const blue = imgData.data[idx + 2]
-        cb([red, green, blue])
-      }
-      img.src = dataUri
-
-    }, `data:image/png;base64,${screenshotData}`, [x, y], [800, 796])
-
-    return { red, green, blue }
-  }
-
-  async switchIsChecked(cssSelector){
-    if (!cssSelector) throw new Error(`switchChecked method requies css selector`)
-    const checked = await this._browser
-      .findElement( By.css(cssSelector) )
-      .getAttribute('aria-checked')
-    return checked === 'true'
-  }
-
-  async click(cssSelector){
-    return await polyFillClick.bind(this)(cssSelector)
-  }
-
-  async getText(cssSelector){
-    if (!cssSelector) throw new Error(`getText needs to define css selector`)
-    const el = await this._browser.findElement( By.css(cssSelector) )
-    
-    const text = await el.getText()
-    return text
-  }
-
-  async areVisible(cssSelector) {
-
-    if (!cssSelector) throw new Error(`getText needs to define css selector`)
-    const els = await this._browser.findElements( By.css(cssSelector) )
-
-    const returnArr = []
-
-    for (const el of els) {
-      const isDisplayed = await el.isDisplayed()
-      if (isDisplayed) returnArr.push(true)
-      else returnArr.push(false)
-    }
-
-    return returnArr
-  }
-
-  async isVisible(cssSelector) {
-
-    if (!cssSelector) throw new Error(`getText needs to define css selector`)
-    const el = await this._browser.findElement( By.css(cssSelector) )
-    const isDisplayed = await el.isDisplayed()
-
-    return isDisplayed
-  }
-
-  async areVisible(cssSelector){
-    if (!cssSelector) throw new Error(`getText needs to define css selector`)
-    const els = await this._browser.findElements( By.css( cssSelector ) )
-    const returnArr = []
-
-    for (const el of els) {
-      returnArr.push(await el.isDisplayed())
-    }
-    return returnArr
-  }
-
-  async isAt(cssSelector){
-    if (!cssSelector) throw new Error(`getText needs to define css selector`)
-    const { x, y, width, height } = await this._browser.findElement( By.css(cssSelector) ).getRect()
-    return { x, y, width, height }
-  }
-
-  async areAt(cssSelector){
-
-    if (!cssSelector) throw new Error(`getText needs to define css selector`)
-    const els = await this._browser.findElements( By.css( cssSelector ) )
-    const returnArr = []
-
-    for (const el of els) {
-      const { x, y, width, height } = await el.getRect()
-      returnArr.push({ x, y, width, height })
-    }
-    return returnArr
-  }
-
-  historyBack() {
-    return this._browser.navigate().back()
-  }
-
-  historyForward() {
-    return this._browser.navigate().forward()
-  }
-
-  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 waitForAsync(){
-
-    const checkReady = async () => {
-      const els = await this._browser.findElements(
-        By.css('.spinnerAnimationCircle')
-      )
-      const visibleEls = []
-      for (const el of els) {
-        if (await el.isDisplayed()) {
-          visibleEls.push(el)
-        }
-      }
-      return !visibleEls.length
-    }
-
-    do {
-      await this.wait(500)
-    } while (
-      !(await retry(checkReady.bind(this), { timeout: 1000, retries: 10 }))
-    )
-  }
-
-  async cursorMoveTo({ position }) {
-    const { x, y } = verifyPosition(position)
-    return this._driver.actions()
-      .move()
-      .move({
-        x,
-        y,
-        duration: 1000
-      })
-      .perform()
-  }
-
-  async cursorMoveToElement(cssSelector) {
-    if (!cssSelector) throw new Error(`cursorMoveToElement needs to define css selector`)
-    const el = await this._browser.findElement( By.css(cssSelector) )
-    await this._driver.actions()
-      .move()
-      .move({
-        origin: el,
-        duration: 1000
-      })
-      .perform()
-  }
-
-  async scrollElementBy(cssSelector, options) {
-    const { delta } = options
-    await this._browser.executeScript(() => {
-      const { delta } = arguments[1]
-      const el = document.querySelector(arguments[0])
-      el.scrollBy(...delta)
-    }, cssSelector, { delta })
-  }
-
-  async getScrollStatus(cssSelector) {
-    const val = await this._browser.executeScript(() => {
-      const el = document.querySelector(arguments[0])
-      return el.scrollTop
-    }, cssSelector)
-    return val
-  }
-
-  async cursorMoveToAndClick({ position }) {
-    const { x, y } = verifyPosition(position)
-    return this._driver.actions()
-      .move()
-      .move({
-        x,
-        y,
-        duration: 1000
-      })
-      .click()
-      .perform()
-  }
-
-  async cursorMoveToAndDrag({ position, delta }) {
-    const { x, y } = verifyPosition(position)
-    const { x: deltaX, y: deltaY } = verifyPosition(delta)
-    return this._driver.actions()
-      .move()
-      .move({
-        x,
-        y,
-        duration: 1000
-      })
-      .press()
-      .move({
-        x: x + deltaX,
-        y: y + deltaY,
-        duration: 1000
-      })
-      .release()
-      .perform()
-  }
-
-  async initHttpInterceptor(){
-    await this._browser.executeScript(() => {
-      if (window.__isIntercepting__) return
-      window.__isIntercepting__ = true
-      const open = window.XMLHttpRequest.prototype.open
-      window.__interceptedXhr__ = []
-      window.XMLHttpRequest.prototype.open = function () {
-        window.__interceptedXhr__.push({
-          method: arguments[0],
-          url: arguments[1]
-        })
-        return open.apply(this, arguments)
-      }
-    })
-  }
-
-  async isHttpIntercepting(){
-    return await this._browser.executeScript(() => {
-      return window.__isIntercepting__
-    })
-  }
-
-  async getInterceptedHttpCalls(){
-    return await this._browser.executeScript(() => {
-      return window['__interceptedXhr__']
-    })
-  }
-
-  // it seems if you set intercept http to be true, you might also want ot set do not automat to be true
-  async goto(url = '/', { interceptHttp, doNotAutomate, forceTimeout = 20 * 1000 } = {}){
-    this.__trackingNavigationState__ = false
-    const actualUrl = getActualUrl(url)
-    if (interceptHttp) {
-      this._browser.get(actualUrl)
-      await this.initHttpInterceptor() 
-    } else {
-      await this._browser.get(actualUrl)
-    }
-
-    // if doNotAutomate is not set
-    // should wait for async operations to end 
-    if (!doNotAutomate) {
-      await this.wait(200)
-      await this.dismissModal()
-      await this.wait(200)
-
-      if (forceTimeout) {
-        await Promise.race([
-          this.waitForAsync(),
-          this.wait(forceTimeout)
-        ])
-      } else {
-        await this.waitForAsync()
-      }
-    }
-  }
-
-  async wait(ms) {
-    if (!ms) throw new Error(`wait duration must be specified!`)
-    return new Promise(rs => {
-      setTimeout(rs, ms)
-    })
-  }
-
-  async waitForCss(cssSelector) {
-    if (!cssSelector) throw new Error(`css selector must be defined`)
-    await this._browser.wait(
-      until.elementLocated( By.css(cssSelector) ),
-      1e3 * 60 * 10
-    )
-  }
-
-  async waitFor(animation = false, async = false){
-    if (animation) await this.wait(500)
-    if (async) await this.waitForAsync()
-  }
-
-  async getSnackbarMessage(){
-    const txt = await this._driver
-      .findElement( By.tagName('simple-snack-bar') )
-      .findElement( By.tagName('span') )
-      .getText()
-    return txt
-  }
-
-  async clickSnackbarAction(){
-    await this._driver
-      .findElement( By.tagName('simple-snack-bar') )
-      .findElement( By.tagName('button') )
-      .click()
-  }
-
-  _getBottomSheet() {
-    return this._driver.findElement( By.tagName('mat-bottom-sheet-container') )
-  }
-
-  _getBottomSheetList(){
-    return this._getBottomSheet().findElements( By.tagName('mat-list-item') )
-  }
-
-  async getBottomSheetList(){
-    const listItems = await this._getBottomSheetList()
-    const output = []
-    for (const item of listItems) {
-      output.push(
-        await _getTextFromWebElement(item)
-      )
-    }
-    return output
-  }
-
-  async clickNthItemFromBottomSheetList(index, cssSelector){
-
-    const list = await this._getBottomSheetList()
-
-    if (!list[index]) throw new Error(`index out of bound: ${index} in list with size ${list.length}`)
-
-    if (cssSelector) {
-      await list[index]
-        .findElement( By.css(cssSelector) )
-        .click()
-    } else {
-      await list[index].click()
-    }
-  }
-
-  async clearAlerts() {
-    await this._driver
-      .actions()
-      .sendKeys(
-        Key.ESCAPE,
-        Key.ESCAPE,
-        Key.ESCAPE,
-        Key.ESCAPE
-      )
-      .perform()
-
-    await this.wait(500)
-  }
-
-  async execScript(fn, ...arg){
-    const result = await this._driver.executeScript(fn)
-    return result
-  }
-}
-
-class WdLayoutPage extends WdBase{
-  constructor(){
-    super()
-    WdLayoutPage.TagNames = {
-      ...(WdBase.TagNames || {} )
-    }
-  }
-
-  _getModal(){
-    return this._browser.findElement( By.tagName('mat-dialog-container') )
-  }
-
-  async dismissModal() {
-    try {
-      const okBtn = await this._getModal()
-        .findElement( By.tagName('mat-dialog-actions') )
-        .findElement( By.css('button[color="primary"]') )
-      await okBtn.click()
-    } catch (e) {
-      
-    }
-  }
-
-  _getModalBtns(){
-    return this._getModal().findElements( By.tagName('button') )
-  }
-
-  async getModalText(){
-    const el = await this._getModal()
-    const txt = await _getTextFromWebElement(el)
-    return txt
-  }
-
-  async getModalActions(){
-    const btns = await this._getModalBtns()
-
-    const arr = []
-    for (const btn of btns){
-      arr.push(await _getTextFromWebElement(btn))
-    }
-    return arr
-  }
-
-  async modalHasChild(cssSelector){
-    try {
-      const isDisplayed = await this._getModal()
-        .findElement( By.css( cssSelector ) )
-        .isDisplayed()
-      return isDisplayed
-    } catch (e) {
-      return false
-    }
-  }
-
-  // text can be instance of regex or string
-  async clickModalBtnByText(text){
-    const btns = await this._getModalBtns()
-    const arr = await this.getModalActions()
-    if (typeof text === 'string') {
-      const idx = arr.indexOf(text)
-      if (idx < 0) throw new Error(`clickModalBtnByText: ${text} not found.`)
-      await btns[idx].click()
-      return
-    }
-    if (text instanceof RegExp) {
-      const idx = arr.findIndex(item => text.test(item))
-      if (idx < 0) throw new Error(`clickModalBtnByText: regexp ${text.toString()} not found`)
-      await btns[idx].click()
-      return
-    }
-
-    throw new Error(`clickModalBtnByText arg must be instance of string or regexp`)
-  }
-
-  async _findTitleCard(title) {
-    const titleCards = await this._browser
-      .findElement( By.css('ui-splashscreen') )
-      .findElements( By.css('mat-card') )
-    const idx = await _getIndexFromArrayOfWebElements(title, titleCards)
-    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){
-    throw new Error(`selectTitleTemplateParcellation has been deprecated. use selectAtlasTemplateParcellation`)
-  }
-
-  /**
-   * _setAtlasSelectorExpanded
-   * toggle/set the open state of the atlas-layer-selector element
-   * If the only argument (flag) is not provided, it will toggle the atlas-layer-selector
-   * 
-   * Will throw if atlas-layer-selector is not in the DOM
-   * 
-   * @param {boolean} flag 
-   * 
-   */
-  async _setAtlasSelectorExpanded(flag) {
-    const atlasLayerSelectorEl = this._browser.findElement(
-      By.css('atlas-layer-selector')
-    )
-    const openedFlag = (await atlasLayerSelectorEl.getAttribute('data-opened')) === 'true'
-    if (typeof flag === 'undefined' || flag !== openedFlag) {
-      await atlasLayerSelectorEl.findElement(By.css(`button[aria-label="${ARIA_LABELS.TOGGLE_ATLAS_LAYER_SELECTOR}"]`)).click()
-    }
-  }
-
-  async changeTemplate(templateName){
-    if (!templateName) throw new Error(`templateName needs to be provided`)
-    await this._setAtlasSelectorExpanded(true)
-    await this.wait(1000)
-    const allTiles = await this._browser
-      .findElement( By.css('atlas-layer-selector') )
-      .findElements( By.css(`mat-grid-tile`) )
-
-    const idx = await _getIndexFromArrayOfWebElements(templateName, allTiles)
-    if (idx >= 0) await allTiles[idx].click()
-    else throw new Error(`#changeTemplate: templateName ${templateName} cannot be found.`)
-  }
-
-  async changeParc(parcName) {
-    throw new Error(`changeParc NYI`)
-  }
-
-  async selectAtlasTemplateParcellation(atlasName, templateName, parcellationName, parcVersion) {
-    if (!atlasName) throw new Error(`atlasName needs to be provided`)
-    try {
-      /**
-       * if at title screen
-       */
-      await (await this._findTitleCard(atlasName)).click()
-    } catch (e) {
-      /**
-       * if not at title screen
-       * select from dropdown
-       */
-    }
-
-    if (templateName) {
-      await this.wait(1000)
-      await this.waitUntilAllChunksLoaded()
-      await this.changeTemplate(templateName)
-    }
-    
-    if (parcellationName) {
-      await this.wait(1000)
-      await this.waitUntilAllChunksLoaded()
-      await this.changeParc(parcellationName)
-    }
-
-    await this._setAtlasSelectorExpanded(false)
-  }
-
-  // SideNav
-  _getSideNavPrimary(){
-    return this._browser.findElement(
-      By.css('mat-drawer[data-mat-drawer-primary-open]')
-    )
-  }
-
-  async _getSideNavPrimaryExpanded(){
-    return (await this._getSideNavPrimary()
-      .getAttribute('data-mat-drawer-primary-open')) === 'true'
-  }
-
-  _getSideNavSecondary(){
-    return this._browser.findElement(
-      By.css('mat-drawer[data-mat-drawer-secondary-open]')
-    )
-  }
-
-  async _getSideNavSecondaryExpanded(){
-    return (await this._getSideNavSecondary()
-      .getAttribute('data-mat-drawer-secondary-open')) === 'true'
-  }
-
-  async _setSideNavPrimaryExpanded(flag) {
-    const matDrawerPrimaryEl = this._getSideNavPrimary()
-    const openedFlag = await this._getSideNavPrimaryExpanded()
-    if (typeof flag === 'undefined' || flag !== openedFlag) {
-      await this._browser.findElement(By.css(`button[aria-label="${ARIA_LABELS.TOGGLE_SIDE_PANEL}"]`)).click()
-    }
-  }
-
-  _getSideNav() {
-    throw new Error(`side bar no longer exist`)
-  }
-
-  async getSideNavTag(){
-    return await this._browser
-      .findElement( By.css('[mat-drawer-trigger]') )
-      .findElement( By.tagName('i') )
-  }
-
-  sideNavIsVisible(){
-    return this._getSideNav().isDisplayed()
-  }
-
-  // SideNavTag
-  _getSideNavTab(){
-    return this._browser
-      .findElement( By.css('[mat-drawer-trigger]') )
-      .findElement( By.tagName('i') )
-  }
-
-  sideNavTabIsVisible(){
-    return this._getSideNavTab().isDisplayed()
-  }
-
-  clickSideNavTab(){
-    return this._getSideNavTab().click()
-  }
-
-  // statusPanel
-  _getStatusPanel(){
-    return this._browser.findElement( By.css('[mat-drawer-status-panel]') )
-  }
-
-  async statusPanelIsVisible() {
-    try {
-      return await this._getStatusPanel().isDisplayed()
-    } catch (e) {
-      return false
-    }
-  }
-
-  clickStatusPanel() {
-    // Will throw if status panel is not visible
-    return this._getStatusPanel().click()
-  }
-
-  // will throw if sidenav is not visible
-  async getTemplateInfo(){
-    const ariaText = `Hover to find out more info on the selected template`
-    const infoBtn = await this._getSideNav()
-      .findElement( By.css(`[aria-label="${ariaText}"]`) )
-    
-    await this._driver.actions()
-      .move()
-      .move({
-        origin: infoBtn,
-        duration: 1000
-      })
-      .perform()
-
-    await this.wait(500)
-    const text = await this._getSideNav()
-      .findElement( By.id('selected-template-detailed-info') )
-      .getText()
-
-    return text
-  }
-
-  _getAdditionalLayerControl(){
-    return this._browser.findElement(
-      By.css(`[aria-label="${ARIA_LABELS.ADDITIONAL_VOLUME_CONTROL}"]`)
-    )
-  }
-
-  async additionalLayerControlIsVisible(){
-    try {
-      return await this._getAdditionalLayerControl().isDisplayed()
-    } catch (e) {
-      return false
-    }
-  }
-
-  // will throw if additional layer control is not visible
-  additionalLayerControlIsExpanded() {
-    return this._getAdditionalLayerControl()
-      .findElement(
-        By.css('layer-browser')
-      )
-      .isDisplayed()
-  }
-
-  async toggleNthLayerControl(idx) {
-    const els = await this._getAdditionalLayerControl()
-      .findElements( By.css(`[aria-label="${ARIA_LABELS.TOGGLE_SHOW_LAYER_CONTROL}"]`))
-    if (!els[idx]) throw new Error(`toggleNthLayerControl index out of bound: accessor ${idx} with length ${els.length}`)
-    await els[idx].click()
-  }
-
-  // Signin banner
-  _getToolsIcon(){
-    return this._driver
-      .findElement( By.css('[aria-label="Show tools and plugins"]') )
-  }
-
-  async showToolsMenu(){
-    await this._getToolsIcon().click()
-  }
-
-  _getToolsMenu(){
-    return this._driver
-      .findElement( By.css('[aria-label="Tools and plugins menu"]') )
-  }
-
-  _getAllTools(){
-    return this._getToolsMenu().findElements( By.css('[role="menuitem"]') )
-  }
-
-  async getVisibleTools(){
-    // may throw if tools menu not visible
-    const menuItems = await this._getAllTools()
-    const returnArr = []
-    for (const menuItem of menuItems){
-      returnArr.push(
-        await _getTextFromWebElement(menuItem)
-      )
-    }
-    return returnArr
-  }
-
-  async clickOnNthTool(index, cssSelector){
-    const menuItems = await this._getAllTools()
-    if (!menuItems[index]) throw new Error(`index out of bound: accessing index ${index} of length ${menuItems.length}`)
-    if (cssSelector) await menuItems[index].findElement( By.css(cssSelector) ).click()
-    else await menuItems[index].click()
-  }
-
-  _getFavDatasetIcon(){
-    return this._driver
-      .findElement( By.css('[aria-label="Show pinned datasets"]') )
-  }
-
-  async getNumberOfFavDataset(){
-    const attr = await this._getFavDatasetIcon().getAttribute('pinned-datasets-length')
-    return Number(attr)
-  }
-
-  async showPinnedDatasetPanel(){
-    await this._getFavDatasetIcon().click()
-    await this.wait(500)
-  }
-
-  _getPinnedDatasetPanel(){
-    return this._driver
-      .findElement(
-        By.css('[aria-label="Pinned datasets panel"]')
-      )
-  }
-
-  async getPinnedDatasetsFromOpenedPanel(){
-    const list = await this._getPinnedDatasetPanel()
-      .findElements(
-        By.tagName('mat-list-item')
-      )
-
-    const returnArr = []
-    for (const el of list) {
-      const text = await _getTextFromWebElement(el)
-      returnArr.push(text)
-    }
-    return returnArr
-  }
-
-  async unpinNthDatasetFromOpenedPanel(index){
-    const list = await this._getPinnedDatasetPanel()
-      .findElements(
-        By.tagName('mat-list-item')
-      )
-
-    if (!list[index]) throw new Error(`index out of bound: ${index} in list with size ${list.length}`)
-    await list[index]
-      .findElement( By.css('[aria-label="Toggle pinning this dataset"]') )
-      .click()
-  }
-
-  _getWidgetPanel(title){
-    return this._driver.findElement( By.css(`[aria-label="Widget for ${title}"]`) )
-  }
-
-  async widgetPanelIsDispalyed(title){
-    try {
-      const isDisplayed = await this._getWidgetPanel(title).isDisplayed()
-      return isDisplayed
-    } catch (e) {
-      console.warn(`widgetPanelIsDisplayed error`, e)
-      return false
-    }
-  }
-
-  async closeWidgetByname(title){
-    await this._getWidgetPanel(title)
-      .findElement( By.css(`[aria-label="close"]`) )
-      .click()
-  }
-}
-
-class WdIavPage extends WdLayoutPage{
-  constructor(){
-    super()
-  }
-
-  async clearAllSelectedRegions() {
-    const clearAllRegionBtn = await this._browser.findElement(
-      By.css(`[aria-label="${ARIA_LABELS.CLEAR_SELECTED_REGION}"]`)
-    )
-    await clearAllRegionBtn.click()
-    await this.wait(500)
-  }
-
-  async waitUntilAllChunksLoaded(){
-    await this._browser.wait(async () => {
-      const els = await this._browser.findElements(
-        By.css('div.loadingIndicator')
-      )
-      const els2 = await this._browser.findElements(
-        By.css('.spinnerAnimationCircle')
-      )
-      return [...els, ...els2].length === 0
-    }, 1e3 * 60 * 10)
-  }
-
-  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 this._getSideNav()
-      .findElement( By.tagName('viewer-state-controller') )
-      .findElement( By.css('[aria-label="Select a new template"]') )
-    await templateBtn.click()
-
-    await this._browser.wait(
-      until.elementLocated( By.tagName('mat-option') ),
-      1e3 * 60 * 10
-    )
-
-    const options = await this._browser.findElements(
-      By.tagName('mat-option')
-    )
-    const idx = await _getIndexFromArrayOfWebElements(title, options)
-    if (idx >= 0) {
-      retry(async () => {
-        await options[idx].click()
-      }, { timeout: 1000, retries: 3 })
-    }
-    else throw new Error(`${title} is not found as one of the dropdown templates`)
-  }
-
-  async _getSearchRegionInput(){
-    await this._setSideNavPrimaryExpanded(true)
-    await this.wait(500)
-    const secondaryOpen = await this._getSideNavSecondaryExpanded()
-    if (secondaryOpen) {
-      return this._getSideNavSecondary().findElement( By.css(`[aria-label="${ARIA_LABELS.TEXT_INPUT_SEARCH_REGION}"]`) )
-    } else {
-      return this._getSideNavPrimary().findElement( By.css(`[aria-label="${ARIA_LABELS.TEXT_INPUT_SEARCH_REGION}"]`) )
-    }
-  }
-
-  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 _getAutcompleteOptions(){
-    const input = await this._getSearchRegionInput()
-    const autocompleteId = await input.getAttribute('aria-owns')
-    const el = await this._browser.findElement( By.css( `[id=${autocompleteId}]` ) )
-    return this._browser
-      .findElement( By.id( autocompleteId ) )
-      .findElements( By.tagName('mat-option') )
-  }
-
-  async getSearchRegionInputAutoCompleteOptions(){
-    const options = await this._getAutcompleteOptions()
-    return await Promise.all(
-      options.map(_getTextFromWebElement)
-    )
-  }
-
-  async selectSearchRegionAutocompleteWithText(text = ''){
-
-    const options = await this._getAutcompleteOptions()
-
-    const idx = await _getIndexFromArrayOfWebElements(text, options)
-    if (idx >= 0) {
-      await options[idx].click()
-    } else {
-      throw new Error(`_getIndexFromArrayOfWebElements ${text} option not founds`)
-    }
-  }
-
-  _getModalityListView(){
-    return this._browser
-      .findElement( By.css('modality-picker') )
-      .findElements( By.css('mat-checkbox') )
-  }
-
-  async getModalities(){
-    const els = await this._getModalityListView()
-    const returnArr = []
-    for (const el of els) {
-      returnArr.push(
-        await el.getText()
-      )
-    }
-    return returnArr
-  }
-
-  _getSingleDatasetListView(){
-    return this._browser
-      .findElement( By.css('data-browser') )
-      .findElements( By.css('single-dataset-list-view') )
-  }
-
-  _getRegionalFeatureEl(){
-    return this._getSideNavSecondary().findElement(
-      By.css(`mat-expansion-panel[data-mat-expansion-title="${CONST.REGIONAL_FEATURES}"]`)
-    )
-  }
-
-  async _setRegionalFeaturesExpanded(flag){
-    const regionFeatureExpEl = this._getRegionalFeatureEl()
-    const openedFlag = (await regionFeatureExpEl.getAttribute('data-opened')) === 'true'
-    if (typeof flag === 'undefined' || flag !== openedFlag) {
-      await regionFeatureExpEl.findElement(By.css(`mat-expansion-panel-header`)).click()
-    }
-  }
-
-  async getVisibleDatasets() {
-    const singleDatasetListView = await this._getSingleDatasetListView()
-
-    const returnArr = []
-    for (const item of singleDatasetListView) {
-      returnArr.push( await item.getText() )
-    }
-    return returnArr
-  }
-
-  async clickNthDataset(index){
-    if (!Number.isInteger(index)) throw new Error(`index needs to be an integer`)
-    const list = await this._getSingleDatasetListView()
-    await list[index].click()
-  }
-
-  async togglePinNthDataset(index) {
-    if (!Number.isInteger(index)) throw new Error(`index needs to be an integer`)
-    const list = await this._getSingleDatasetListView()
-    if (!list[index]) throw new Error(`out of bound ${index} in list with length ${list.length}`)
-    await list[index]
-      .findElement( By.css('[aria-label="Toggle pinning this dataset"]') )
-      .click()
-  }
-
-  async viewerIsPopulated() {
-    try {
-      const ngContainer = await this._browser.findElement(
-        By.id('neuroglancer-container')
-      )
-      if (! (await ngContainer.isDisplayed())) {
-        return false
-      }
-      const canvas = await ngContainer.findElement(
-        By.tagName('canvas')
-      )
-      if (!(await canvas.isDisplayed())) {
-        return false
-      }
-      return true
-    } catch (e) {
-      return false
-    }
-  }
-
-  async getNavigationState() {
-    if (!this.__trackingNavigationState__) {
-      await this._browser.executeScript(async () => {
-        window.__iavE2eNavigationState__ = {}
-        
-        const getPr = () => new Promise(rs => {
-  
-          window.__iavE2eNavigationStateSubptn__ = nehubaViewer.navigationState.all
-            .subscribe(({ orientation, perspectiveOrientation, perspectiveZoom, position, zoom }) => {
-              window.__iavE2eNavigationState__ = {
-                orientation: Array.from(orientation),
-                perspectiveOrientation: Array.from(perspectiveOrientation),
-                perspectiveZoom,
-                zoom,
-                position: Array.from(position)
-              }
-              rs()
-            })
-        })
-  
-        await getPr()
-      })
-
-      this.__trackingNavigationState__ = true
-    }
-
-    const returnVal = await this._browser.executeScript(() => window.__iavE2eNavigationState__)
-    return returnVal
-  }
-
-}
-
-class PptrIAVPage{
-
-  constructor(){
-    this._browser = null
-    this._page = null
-  }
-
-  async init() {
-    this._browser = browser
-
-    this._page = await this._browser.newPage()
-    await this._page.setViewport({
-      width: 1600,
-      height: 900
-    })
-  }
-
-  async goto(url = '/') {
-    const actualUrl = getActualUrl(url)
-    await this._page.goto(actualUrl, { waitUntil: 'networkidle2' })
-  }
-
-  async wait(ms) {
-    if (!ms) throw new Error(`wait duration must be specified!`)
-    await this._page.waitFor(ms)
-  }
-}
-
-exports.waitMultiple = process.env.WAIT_ULTIPLE || 1
-
-exports.AtlasPage = WdIavPage
-exports.LayoutPage = WdLayoutPage
+exports.AtlasPage = AtlasPage
+exports.LayoutPage = LayoutPage
diff --git a/e2e/util/helper.js b/e2e/util/helper.js
new file mode 100644
index 0000000000000000000000000000000000000000..10a7176ad2c2428057a87561e0e1427f76e5ea84
--- /dev/null
+++ b/e2e/util/helper.js
@@ -0,0 +1,11 @@
+const {
+  WdIavPage,
+  WdLayoutPage,
+  WdBase,
+} = require('./selenium/iav')
+
+module.exports = {
+  BasePage: WdBase,
+  LayoutPage: WdLayoutPage,
+  AtlasPage: WdIavPage
+}
diff --git a/e2e/util/selenium/base.js b/e2e/util/selenium/base.js
new file mode 100644
index 0000000000000000000000000000000000000000..c7172156bb35fb1df676433916fc91c46f41bc60
--- /dev/null
+++ b/e2e/util/selenium/base.js
@@ -0,0 +1,408 @@
+const CITRUS_LIGHT_URL = `https://unpkg.com/citruslight@0.1.0/citruslight.js`
+const { By, Key, until } = require('selenium-webdriver')
+const { retry } = require('../../../common/util')
+const chromeOpts = require('../../chromeOpts')
+const ATLAS_URL = (process.env.ATLAS_URL || 'http://localhost:3000').replace(/\/$/, '')
+
+function getActualUrl(url) {
+  return /^http\:\/\//.test(url) ? url : `${ATLAS_URL}/${url.replace(/^\//, '')}`
+}
+
+async function polyFillClick(cssSelector){
+  if (!cssSelector) throw new Error(`click method needs to define a css selector`)
+  const webEl = this._browser.findElement( By.css(cssSelector) )
+  try {
+    await webEl.click()
+  } catch (e) {
+    const id = await webEl.getAttribute('id')
+    const newId = id.replace(/-input$/, '')
+    await this._browser.findElement(
+      By.id(newId)
+    ).click()
+  }
+}
+
+const verifyPosition = 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 {
+    x,
+    y
+  }
+}
+
+class WdBase{
+  constructor() {
+    browser.waitForAngularEnabled(false)
+  }
+  get _browser(){
+    return browser
+  }
+  get _driver(){
+    return this._browser.driver
+  }
+
+  // without image header
+  // output as b64 png
+  async takeScreenshot(cssSelector){
+    
+    if(cssSelector) {
+      await this._browser.executeAsyncScript(async () => {
+        const cb = arguments[arguments.length - 1]
+        const moduleUrl = arguments[0]
+        const cssSelector = arguments[1]
+
+        const el = document.querySelector(cssSelector)
+        if (!el) throw new Error(`css selector not fetching anything`)
+        import(moduleUrl)
+          .then(async m => {
+            m.citruslight(el)
+            cb()
+          })
+      }, CITRUS_LIGHT_URL, cssSelector)
+    }
+    
+    await this.wait(1000)
+    const result = await this._browser.takeScreenshot()
+
+    if (cssSelector) {
+      await this._browser.executeAsyncScript(async () => {
+        const cb = arguments[arguments.length - 1]
+        const moduleUrl = arguments[0]
+        const cssSelector = arguments[1]
+
+        const el = document.querySelector(cssSelector)
+        if (!el) throw new Error(`css selector not fetching anything`)
+        import(moduleUrl)
+          .then(async m => {
+            m.clearAll()
+            cb()
+          })
+      }, CITRUS_LIGHT_URL, cssSelector)
+    }
+    
+    await this.wait(1000)
+    return result
+  }
+
+  async getRgbAt({ position } = {}, cssSelector = null){
+    if (!position) throw new Error(`position is required for getRgbAt`)
+    const { x, y } = verifyPosition(position)
+    const screenshotData = await this.takeScreenshot(cssSelector)
+    const [ red, green, blue ] = await this._driver.executeAsyncScript(() => {
+      
+      const dataUri = arguments[0]
+      const pos = arguments[1]
+      const dim = arguments[2]
+      const cb = arguments[arguments.length - 1]
+
+      const img = new Image()
+      img.onload = () => {
+        const canvas = document.createElement('canvas')
+        canvas.width = dim[0]
+        canvas.height = dim[1]
+
+        const ctx = canvas.getContext('2d')
+        ctx.drawImage(img, 0, 0)
+        const imgData = ctx.getImageData(0, 0, dim[0], dim[1])
+
+        const idx = (dim[0] * pos[1] + pos[0]) * 4
+        const red = imgData.data[idx]
+        const green = imgData.data[idx + 1]
+        const blue = imgData.data[idx + 2]
+        cb([red, green, blue])
+      }
+      img.src = dataUri
+
+    }, `data:image/png;base64,${screenshotData}`, [x, y], [800, 796])
+
+    return { red, green, blue }
+  }
+
+  async switchIsChecked(cssSelector){
+    if (!cssSelector) throw new Error(`switchChecked method requies css selector`)
+    const checked = await this._browser
+      .findElement( By.css(cssSelector) )
+      .getAttribute('aria-checked')
+    return checked === 'true'
+  }
+
+  async click(cssSelector){
+    return await polyFillClick.bind(this)(cssSelector)
+  }
+
+  async getText(cssSelector){
+    if (!cssSelector) throw new Error(`getText needs to define css selector`)
+    const el = await this._browser.findElement( By.css(cssSelector) )
+    
+    const text = await el.getText()
+    return text
+  }
+
+  async isVisible(cssSelector) {
+
+    if (!cssSelector) throw new Error(`getText needs to define css selector`)
+    const el = await this._browser.findElement( By.css(cssSelector) )
+    const isDisplayed = await el.isDisplayed()
+
+    return isDisplayed
+  }
+
+  async areVisible(cssSelector){
+    if (!cssSelector) throw new Error(`getText needs to define css selector`)
+    const els = await this._browser.findElements( By.css( cssSelector ) )
+    const returnArr = []
+
+    for (const el of els) {
+      returnArr.push(await el.isDisplayed())
+    }
+    return returnArr
+  }
+
+  async isAt(cssSelector){
+    if (!cssSelector) throw new Error(`getText needs to define css selector`)
+    const { x, y, width, height } = await this._browser.findElement( By.css(cssSelector) ).getRect()
+    return { x, y, width, height }
+  }
+
+  async areAt(cssSelector){
+
+    if (!cssSelector) throw new Error(`getText needs to define css selector`)
+    const els = await this._browser.findElements( By.css( cssSelector ) )
+    const returnArr = []
+
+    for (const el of els) {
+      const { x, y, width, height } = await el.getRect()
+      returnArr.push({ x, y, width, height })
+    }
+    return returnArr
+  }
+
+  historyBack() {
+    return this._browser.navigate().back()
+  }
+
+  historyForward() {
+    return this._browser.navigate().forward()
+  }
+
+  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 waitForAsync(){
+
+    const checkReady = async () => {
+      const els = await this._browser.findElements(
+        By.css('.spinnerAnimationCircle')
+      )
+      const visibleEls = []
+      for (const el of els) {
+        if (await el.isDisplayed()) {
+          visibleEls.push(el)
+        }
+      }
+      return !visibleEls.length
+    }
+
+    do {
+      await this.wait(500)
+    } while (
+      !(await retry(checkReady.bind(this), { timeout: 1000, retries: 10 }))
+    )
+  }
+
+  async cursorMoveTo({ position }) {
+    const { x, y } = verifyPosition(position)
+    return this._driver.actions()
+      .move()
+      .move({
+        x,
+        y,
+        duration: 1000
+      })
+      .perform()
+  }
+
+  async cursorMoveToElement(cssSelector) {
+    if (!cssSelector) throw new Error(`cursorMoveToElement needs to define css selector`)
+    const el = await this._browser.findElement( By.css(cssSelector) )
+    await this._driver.actions()
+      .move()
+      .move({
+        origin: el,
+        duration: 1000
+      })
+      .perform()
+  }
+
+  async scrollElementBy(cssSelector, options) {
+    const { delta } = options
+    await this._browser.executeScript(() => {
+      const { delta } = arguments[1]
+      const el = document.querySelector(arguments[0])
+      el.scrollBy(...delta)
+    }, cssSelector, { delta })
+  }
+
+  async getScrollStatus(cssSelector) {
+    const val = await this._browser.executeScript(() => {
+      const el = document.querySelector(arguments[0])
+      return el.scrollTop
+    }, cssSelector)
+    return val
+  }
+
+  async cursorMoveToAndClick({ position }) {
+    const { x, y } = verifyPosition(position)
+    return this._driver.actions()
+      .move()
+      .move({
+        x,
+        y,
+        duration: 1000
+      })
+      .click()
+      .perform()
+  }
+
+  async cursorMoveToAndDrag({ position, delta }) {
+    const { x, y } = verifyPosition(position)
+    const { x: deltaX, y: deltaY } = verifyPosition(delta)
+    return this._driver.actions()
+      .move()
+      .move({
+        x,
+        y,
+        duration: 1000
+      })
+      .press()
+      .move({
+        x: x + deltaX,
+        y: y + deltaY,
+        duration: 1000
+      })
+      .release()
+      .perform()
+  }
+
+  async initHttpInterceptor(){
+    await this._browser.executeScript(() => {
+      if (window.__isIntercepting__) return
+      window.__isIntercepting__ = true
+      const open = window.XMLHttpRequest.prototype.open
+      window.__interceptedXhr__ = []
+      window.XMLHttpRequest.prototype.open = function () {
+        window.__interceptedXhr__.push({
+          method: arguments[0],
+          url: arguments[1]
+        })
+        return open.apply(this, arguments)
+      }
+    })
+  }
+
+  async isHttpIntercepting(){
+    return await this._browser.executeScript(() => {
+      return window.__isIntercepting__
+    })
+  }
+
+  async getInterceptedHttpCalls(){
+    return await this._browser.executeScript(() => {
+      return window['__interceptedXhr__']
+    })
+  }
+
+  // it seems if you set intercept http to be true, you might also want ot set do not automat to be true
+  async goto(url = '/', { interceptHttp, doNotAutomate, forceTimeout = 20 * 1000 } = {}){
+    this.__trackingNavigationState__ = false
+    const actualUrl = getActualUrl(url)
+    if (interceptHttp) {
+      this._browser.get(actualUrl)
+      await this.initHttpInterceptor() 
+    } else {
+      await this._browser.get(actualUrl)
+    }
+
+    // if doNotAutomate is not set
+    // should wait for async operations to end 
+    if (!doNotAutomate) {
+      await this.wait(200)
+      await this.dismissModal()
+      await this.wait(200)
+
+      if (forceTimeout) {
+        await Promise.race([
+          this.waitForAsync(),
+          this.wait(forceTimeout)
+        ])
+      } else {
+        await this.waitForAsync()
+      }
+    }
+  }
+
+  async wait(ms) {
+    if (!ms) throw new Error(`wait duration must be specified!`)
+    return new Promise(rs => {
+      setTimeout(rs, ms)
+    })
+  }
+
+  async waitForCss(cssSelector) {
+    if (!cssSelector) throw new Error(`css selector must be defined`)
+    await this._browser.wait(
+      until.elementLocated( By.css(cssSelector) ),
+      1e3 * 60 * 10
+    )
+  }
+
+  async waitFor(animation = false, async = false){
+    if (animation) await this.wait(500)
+    if (async) await this.waitForAsync()
+  }
+
+  async clearAlerts() {
+    await this._driver
+      .actions()
+      .sendKeys(
+        Key.ESCAPE,
+        Key.ESCAPE,
+        Key.ESCAPE,
+        Key.ESCAPE
+      )
+      .perform()
+
+    await this.wait(500)
+  }
+
+  async execScript(fn, ...arg){
+    const result = await this._driver.executeScript(fn)
+    return result
+  }
+}
+
+module.exports = {
+  WdBase
+}
diff --git a/e2e/util/selenium/iav.js b/e2e/util/selenium/iav.js
new file mode 100644
index 0000000000000000000000000000000000000000..2629403e439a8e8998ca115acd02939a9746bb3b
--- /dev/null
+++ b/e2e/util/selenium/iav.js
@@ -0,0 +1,206 @@
+const { WdLayoutPage, WdBase } = require('./layout')
+const { ARIA_LABELS, CONST } = require('../../../common/constants')
+const { _getTextFromWebElement, _getIndexFromArrayOfWebElements } = require('./util')
+const { Key } = require('selenium-webdriver')
+
+class WdIavPage extends WdLayoutPage{
+  constructor(){
+    super()
+  }
+
+  async clearAllSelectedRegions() {
+    const clearAllRegionBtn = await this._browser.findElement(
+      By.css(`[aria-label="${ARIA_LABELS.CLEAR_SELECTED_REGION}"]`)
+    )
+    await clearAllRegionBtn.click()
+    await this.wait(500)
+  }
+
+  async waitUntilAllChunksLoaded(){
+    await this._browser.wait(async () => {
+      const els = await this._browser.findElements(
+        By.css('div.loadingIndicator')
+      )
+      const els2 = await this._browser.findElements(
+        By.css('.spinnerAnimationCircle')
+      )
+      return [...els, ...els2].length === 0
+    }, 1e3 * 60 * 10)
+  }
+
+  async getFloatingCtxInfoAsText(){
+    const floatingContainer = await this._browser.findElement(
+      By.css('div[floatingMouseContextualContainerDirective]')
+    )
+
+    const text = await floatingContainer.getText()
+    return text
+  }
+
+  async selectDropdownTemplate(title) {
+    throw new Error(`selectDropdownTemplate has been deprecated. use changeTemplate instead`)
+  }
+
+  async _getSearchRegionInput(){
+    await this._setSideNavPrimaryExpanded(true)
+    await this.wait(500)
+    const secondaryOpen = await this._getSideNavSecondaryExpanded()
+    if (secondaryOpen) {
+      return this._getSideNavSecondary().findElement( By.css(`[aria-label="${ARIA_LABELS.TEXT_INPUT_SEARCH_REGION}"]`) )
+    } else {
+      return this._getSideNavPrimary().findElement( By.css(`[aria-label="${ARIA_LABELS.TEXT_INPUT_SEARCH_REGION}"]`) )
+    }
+  }
+
+  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 _getAutcompleteOptions(){
+    const input = await this._getSearchRegionInput()
+    const autocompleteId = await input.getAttribute('aria-owns')
+    const el = await this._browser.findElement( By.css( `[id=${autocompleteId}]` ) )
+    return this._browser
+      .findElement( By.id( autocompleteId ) )
+      .findElements( By.tagName('mat-option') )
+  }
+
+  async getSearchRegionInputAutoCompleteOptions(){
+    const options = await this._getAutcompleteOptions()
+    return await Promise.all(
+      options.map(_getTextFromWebElement)
+    )
+  }
+
+  async selectSearchRegionAutocompleteWithText(text = ''){
+
+    const options = await this._getAutcompleteOptions()
+
+    const idx = await _getIndexFromArrayOfWebElements(text, options)
+    if (idx >= 0) {
+      await options[idx].click()
+    } else {
+      throw new Error(`_getIndexFromArrayOfWebElements ${text} option not founds`)
+    }
+  }
+
+  _getModalityListView(){
+    return this._browser
+      .findElement( By.css('modality-picker') )
+      .findElements( By.css('mat-checkbox') )
+  }
+
+  async getModalities(){
+    const els = await this._getModalityListView()
+    const returnArr = []
+    for (const el of els) {
+      returnArr.push(
+        await el.getText()
+      )
+    }
+    return returnArr
+  }
+
+  _getSingleDatasetListView(){
+    return this._browser
+      .findElement( By.css('data-browser') )
+      .findElements( By.css('single-dataset-list-view') )
+  }
+
+  async getVisibleDatasets() {
+    const singleDatasetListView = await this._getSingleDatasetListView()
+
+    const returnArr = []
+    for (const item of singleDatasetListView) {
+      returnArr.push( await item.getText() )
+    }
+    return returnArr
+  }
+
+  async clickNthDataset(index){
+    if (!Number.isInteger(index)) throw new Error(`index needs to be an integer`)
+    const list = await this._getSingleDatasetListView()
+    await list[index].click()
+  }
+
+  async togglePinNthDataset(index) {
+    if (!Number.isInteger(index)) throw new Error(`index needs to be an integer`)
+    const list = await this._getSingleDatasetListView()
+    if (!list[index]) throw new Error(`out of bound ${index} in list with length ${list.length}`)
+    await list[index]
+      .findElement( By.css('[aria-label="Toggle pinning this dataset"]') )
+      .click()
+  }
+
+  async viewerIsPopulated() {
+    try {
+      const ngContainer = await this._browser.findElement(
+        By.id('neuroglancer-container')
+      )
+      if (! (await ngContainer.isDisplayed())) {
+        return false
+      }
+      const canvas = await ngContainer.findElement(
+        By.tagName('canvas')
+      )
+      if (!(await canvas.isDisplayed())) {
+        return false
+      }
+      return true
+    } catch (e) {
+      return false
+    }
+  }
+
+  async getNavigationState() {
+    if (!this.__trackingNavigationState__) {
+      await this._browser.executeScript(async () => {
+        window.__iavE2eNavigationState__ = {}
+        
+        const getPr = () => new Promise(rs => {
+  
+          window.__iavE2eNavigationStateSubptn__ = nehubaViewer.navigationState.all
+            .subscribe(({ orientation, perspectiveOrientation, perspectiveZoom, position, zoom }) => {
+              window.__iavE2eNavigationState__ = {
+                orientation: Array.from(orientation),
+                perspectiveOrientation: Array.from(perspectiveOrientation),
+                perspectiveZoom,
+                zoom,
+                position: Array.from(position)
+              }
+              rs()
+            })
+        })
+  
+        await getPr()
+      })
+
+      this.__trackingNavigationState__ = true
+    }
+
+    const returnVal = await this._browser.executeScript(() => window.__iavE2eNavigationState__)
+    return returnVal
+  }
+
+}
+
+module.exports = {
+  WdIavPage,
+  WdLayoutPage,
+  WdBase,
+}
\ No newline at end of file
diff --git a/e2e/util/selenium/layout.js b/e2e/util/selenium/layout.js
new file mode 100644
index 0000000000000000000000000000000000000000..286504b7f7b888c614c7fff373dfbf97943cbe86
--- /dev/null
+++ b/e2e/util/selenium/layout.js
@@ -0,0 +1,489 @@
+const { WdBase } = require('./base')
+const {
+  _getIndexFromArrayOfWebElements,
+  _getTextFromWebElement
+} = require('./util')
+const { ARIA_LABELS } = require('../../../common/constants')
+
+class WdLayoutPage extends WdBase{
+  constructor(){
+    super()
+  }
+
+  /**
+   * Snackbar
+   */
+  async getSnackbarMessage(){
+    const txt = await this._driver
+      .findElement( By.css('simple-snack-bar') )
+      .findElement( By.css('span') )
+      .getText()
+    return txt
+  }
+
+  async clickSnackbarAction(){
+    await this._driver
+      .findElement( By.css('simple-snack-bar') )
+      .findElement( By.css('button') )
+      .click()
+  }
+
+  /**
+   * Bottomsheet
+   */
+  _getBottomSheet() {
+    return this._driver.findElement( By.css('mat-bottom-sheet-container') )
+  }
+
+  _getBottomSheetList(){
+    return this._getBottomSheet().findElements( By.css('mat-list-item') )
+  }
+
+  async getBottomSheetList(){
+    const listItems = await this._getBottomSheetList()
+    const output = []
+    for (const item of listItems) {
+      output.push(
+        await _getTextFromWebElement(item)
+      )
+    }
+    return output
+  }
+
+  async clickNthItemFromBottomSheetList(index, cssSelector){
+
+    const list = await this._getBottomSheetList()
+
+    if (!list[index]) throw new Error(`index out of bound: ${index} in list with size ${list.length}`)
+
+    if (cssSelector) {
+      await list[index]
+        .findElement( By.css(cssSelector) )
+        .click()
+    } else {
+      await list[index].click()
+    }
+  }
+
+  /**
+   * Modal
+   */
+  _getModal(){
+    return this._browser.findElement( By.css('mat-dialog-container') )
+  }
+
+  async dismissModal() {
+    try {
+      const okBtn = await this._getModal()
+        .findElement( By.css('mat-dialog-actions') )
+        .findElement( By.css('button[color="primary"]') )
+      await okBtn.click()
+    } catch (e) {
+      
+    }
+  }
+
+  _getModalBtns(){
+    return this._getModal().findElements( By.tagName('button') )
+  }
+
+  async getModalText(){
+    const el = await this._getModal()
+    const txt = await _getTextFromWebElement(el)
+    return txt
+  }
+
+  async getModalActions(){
+    const btns = await this._getModalBtns()
+
+    const arr = []
+    for (const btn of btns){
+      arr.push(await _getTextFromWebElement(btn))
+    }
+    return arr
+  }
+
+  /**
+   * 
+   * @param {string|RegExp} text 
+   * @description search criteria for the btn to click
+   */
+  async clickModalBtnByText(text){
+    const btns = await this._getModalBtns()
+    const arr = await this.getModalActions()
+    if (typeof text === 'string') {
+      const idx = arr.indexOf(text)
+      if (idx < 0) throw new Error(`clickModalBtnByText: ${text} not found.`)
+      await btns[idx].click()
+      return
+    }
+    if (text instanceof RegExp) {
+      const idx = arr.findIndex(item => text.test(item))
+      if (idx < 0) throw new Error(`clickModalBtnByText: regexp ${text.toString()} not found`)
+      await btns[idx].click()
+      return
+    }
+
+    throw new Error(`clickModalBtnByText arg must be instance of string or regexp`)
+  }
+
+  /**
+   * ExpansionPanel
+   */
+
+  /**
+   * 
+   * @param {string|RegExp} text 
+   * @description search criteria for the expansion panel title
+   */
+  async _getExpansionPanel(text){
+    const expPanels = await this._browser.findElements(
+      By.css(`mat-expansion-panel`)
+    )
+
+    const expPanelHdrs = []
+    for (const expPanel of expPanels) {
+      expPanelHdrs.push(
+        await expPanel.findElement(
+          By.css(`mat-expansion-panel-header`)
+        )
+      )
+    }
+
+    const idx = await _getIndexFromArrayOfWebElements(text, expPanelHdrs)
+    return idx >= 0 && expPanels[idx]
+  }
+
+  /**
+   * 
+   * @param {Object} expPanel webelement of the mat expansion panel
+   * @returns {Promise<boolean>} 
+   */
+  async _expansionPanelIsOpen(expPanel){
+    const classString = await expPanel.getAttribute('class')
+    /**
+     * mat-expanded gets appended when mat-expansion panel is set to open
+     */
+    return classString.indexOf('mat-expanded') >= 0
+  }
+
+  /**
+   * 
+   * @param {string|RegExp} name Text of the expansion panel header
+   * @param {boolean} flag @optional State to set the expansion panel. Leave empty for toggle.
+   */
+  async toggleExpansionPanelState(name, flag){
+    const expPanel = await this._getExpansionPanel(name)
+    if (!expPanel) throw new Error(`expansionPanel ${name} could not be found`)
+    if (typeof flag === 'undefined') {
+      await expPanel.findElement(
+        By.css(`mat-expansion-panel-header`)
+      ).click()
+      return
+    } 
+    
+    const currentOpen = await this._expansionPanelIsOpen(expPanel)
+    if (currentOpen !== flag) {
+      await expPanel.findElement(
+        By.css(`mat-expansion-panel-header`)
+      ).click()
+    }
+  }
+
+
+  /**
+   * Other
+   */
+  async _findTitleCard(title) {
+    const titleCards = await this._browser
+      .findElement( By.css('ui-splashscreen') )
+      .findElements( By.css('mat-card') )
+    const idx = await _getIndexFromArrayOfWebElements(title, titleCards)
+    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){
+    throw new Error(`selectTitleTemplateParcellation has been deprecated. use selectAtlasTemplateParcellation`)
+  }
+
+  /**
+   * _setAtlasSelectorExpanded
+   * toggle/set the open state of the atlas-layer-selector element
+   * If the only argument (flag) is not provided, it will toggle the atlas-layer-selector
+   * 
+   * Will throw if atlas-layer-selector is not in the DOM
+   * 
+   * @param {boolean} flag 
+   * 
+   */
+  async _setAtlasSelectorExpanded(flag) {
+    const atlasLayerSelectorEl = this._browser.findElement(
+      By.css('atlas-layer-selector')
+    )
+    const openedFlag = (await atlasLayerSelectorEl.getAttribute('data-opened')) === 'true'
+    if (typeof flag === 'undefined' || flag !== openedFlag) {
+      await atlasLayerSelectorEl.findElement(By.css(`button[aria-label="${ARIA_LABELS.TOGGLE_ATLAS_LAYER_SELECTOR}"]`)).click()
+    }
+  }
+
+  async changeTemplate(templateName){
+    if (!templateName) throw new Error(`templateName needs to be provided`)
+    await this._setAtlasSelectorExpanded(true)
+    await this.wait(1000)
+    const allTiles = await this._browser
+      .findElement( By.css('atlas-layer-selector') )
+      .findElements( By.css(`mat-grid-tile`) )
+
+    const idx = await _getIndexFromArrayOfWebElements(templateName, allTiles)
+    if (idx >= 0) await allTiles[idx].click()
+    else throw new Error(`#changeTemplate: templateName ${templateName} cannot be found.`)
+  }
+
+  async changeParc(parcName) {
+    throw new Error(`changeParc NYI`)
+  }
+
+  async selectAtlasTemplateParcellation(atlasName, templateName, parcellationName, parcVersion) {
+    if (!atlasName) throw new Error(`atlasName needs to be provided`)
+    try {
+      /**
+       * if at title screen
+       */
+      await (await this._findTitleCard(atlasName)).click()
+    } catch (e) {
+      /**
+       * if not at title screen
+       * select from dropdown
+       */
+    }
+
+    if (templateName) {
+      await this.wait(1000)
+      await this.waitUntilAllChunksLoaded()
+      await this.changeTemplate(templateName)
+    }
+    
+    if (parcellationName) {
+      await this.wait(1000)
+      await this.waitUntilAllChunksLoaded()
+      await this.changeParc(parcellationName)
+    }
+
+    await this._setAtlasSelectorExpanded(false)
+  }
+
+  /**
+   * Sidenav
+   */
+  _getSideNavPrimary(){
+    return this._browser.findElement(
+      By.css('mat-drawer[data-mat-drawer-primary-open]')
+    )
+  }
+
+  async _getSideNavPrimaryExpanded(){
+    return (await this._getSideNavPrimary()
+      .getAttribute('data-mat-drawer-primary-open')) === 'true'
+  }
+
+  _getSideNavSecondary(){
+    return this._browser.findElement(
+      By.css('mat-drawer[data-mat-drawer-secondary-open]')
+    )
+  }
+
+  async _getSideNavSecondaryExpanded(){
+    return (await this._getSideNavSecondary()
+      .getAttribute('data-mat-drawer-secondary-open')) === 'true'
+  }
+
+  async _setSideNavPrimaryExpanded(flag) {
+    const openedFlag = await this._getSideNavPrimaryExpanded()
+    if (typeof flag === 'undefined' || flag !== openedFlag) {
+      await this._browser.findElement(By.css(`button[aria-label="${ARIA_LABELS.TOGGLE_SIDE_PANEL}"]`)).click()
+    }
+  }
+
+  async getSideNavTag(){
+    return await this._browser
+      .findElement( By.css('[mat-drawer-trigger]') )
+      .findElement( By.css('i') )
+  }
+
+  sideNavIsVisible(){
+    throw new Error(`sideNavIsVisible is deprecated`)
+  }
+
+  _getSideNavTab(){
+    return this._browser
+      .findElement( By.css('[mat-drawer-trigger]') )
+      .findElement( By.css('i') )
+  }
+
+  sideNavTabIsVisible(){
+    return this._getSideNavTab().isDisplayed()
+  }
+
+  clickSideNavTab(){
+    return this._getSideNavTab().click()
+  }
+
+  /**
+   * StatusPanel
+   */
+  _getStatusPanel(){
+    return this._browser.findElement( By.css('[mat-drawer-status-panel]') )
+  }
+
+  async statusPanelIsVisible() {
+    try {
+      return await this._getStatusPanel().isDisplayed()
+    } catch (e) {
+      return false
+    }
+  }
+
+  clickStatusPanel() {
+    // Will throw if status panel is not visible
+    return this._getStatusPanel().click()
+  }
+
+  async getTemplateInfo(){
+    throw new Error(`getTemplateInfo has been deprecated. Implmenet new method of getting info`)
+  }
+
+  // will throw if additional layer control is not visible
+  additionalLayerControlIsExpanded() {
+    throw new Error(`additionalLayerControlIsExpanded is deprecated`)
+  }
+
+  /**
+   * TODO? deprecate
+   */
+  async toggleNthLayerControl(idx) {
+    const els = await this._browser
+      .findElement(
+        By.css(`[aria-label="${ARIA_LABELS.ADDITIONAL_VOLUME_CONTROL}"]`)
+      )
+      .findElements( By.css(`[aria-label="${ARIA_LABELS.TOGGLE_SHOW_LAYER_CONTROL}"]`))
+    if (!els[idx]) throw new Error(`toggleNthLayerControl index out of bound: accessor ${idx} with length ${els.length}`)
+    await els[idx].click()
+  }
+
+  // Signin banner
+  _getToolsIcon(){
+    return this._driver
+      .findElement( By.css('[aria-label="Show tools and plugins"]') )
+  }
+
+  async showToolsMenu(){
+    await this._getToolsIcon().click()
+  }
+
+  _getToolsMenu(){
+    return this._driver
+      .findElement( By.css('[aria-label="Tools and plugins menu"]') )
+  }
+
+  _getAllTools(){
+    return this._getToolsMenu().findElements( By.css('[role="menuitem"]') )
+  }
+
+  async getVisibleTools(){
+    // may throw if tools menu not visible
+    const menuItems = await this._getAllTools()
+    const returnArr = []
+    for (const menuItem of menuItems){
+      returnArr.push(
+        await _getTextFromWebElement(menuItem)
+      )
+    }
+    return returnArr
+  }
+
+  async clickOnNthTool(index, cssSelector){
+    const menuItems = await this._getAllTools()
+    if (!menuItems[index]) throw new Error(`index out of bound: accessing index ${index} of length ${menuItems.length}`)
+    if (cssSelector) await menuItems[index].findElement( By.css(cssSelector) ).click()
+    else await menuItems[index].click()
+  }
+
+  _getFavDatasetIcon(){
+    return this._driver
+      .findElement( By.css('[aria-label="Show pinned datasets"]') )
+  }
+
+  async getNumberOfFavDataset(){
+    const attr = await this._getFavDatasetIcon().getAttribute('pinned-datasets-length')
+    return Number(attr)
+  }
+
+  async showPinnedDatasetPanel(){
+    await this._getFavDatasetIcon().click()
+    await this.wait(500)
+  }
+
+  _getPinnedDatasetPanel(){
+    return this._driver
+      .findElement(
+        By.css('[aria-label="Pinned datasets panel"]')
+      )
+  }
+
+  async getPinnedDatasetsFromOpenedPanel(){
+    const list = await this._getPinnedDatasetPanel()
+      .findElements(
+        By.tagName('mat-list-item')
+      )
+
+    const returnArr = []
+    for (const el of list) {
+      const text = await _getTextFromWebElement(el)
+      returnArr.push(text)
+    }
+    return returnArr
+  }
+
+  async unpinNthDatasetFromOpenedPanel(index){
+    const list = await this._getPinnedDatasetPanel()
+      .findElements(
+        By.tagName('mat-list-item')
+      )
+
+    if (!list[index]) throw new Error(`index out of bound: ${index} in list with size ${list.length}`)
+    await list[index]
+      .findElement( By.css('[aria-label="Toggle pinning this dataset"]') )
+      .click()
+  }
+
+  _getWidgetPanel(title){
+    return this._driver.findElement( By.css(`[aria-label="Widget for ${title}"]`) )
+  }
+
+  async widgetPanelIsDispalyed(title){
+    try {
+      const isDisplayed = await this._getWidgetPanel(title).isDisplayed()
+      return isDisplayed
+    } catch (e) {
+      console.warn(`widgetPanelIsDisplayed error`, e)
+      return false
+    }
+  }
+
+  async closeWidgetByname(title){
+    await this._getWidgetPanel(title)
+      .findElement( By.css(`[aria-label="close"]`) )
+      .click()
+  }
+}
+
+module.exports = {
+  WdLayoutPage,
+  WdBase,
+}
\ No newline at end of file
diff --git a/e2e/util/selenium/util.js b/e2e/util/selenium/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..b08adb34bea8a064c397a9ab76209c4f73cf65d4
--- /dev/null
+++ b/e2e/util/selenium/util.js
@@ -0,0 +1,18 @@
+
+function _getTextFromWebElement(webElement) {
+  return webElement.getText()
+}
+
+async function _getIndexFromArrayOfWebElements(search, webElements) {
+  const texts = await Promise.all(
+    webElements.map(_getTextFromWebElement)
+  )
+  return texts.findIndex(text => search instanceof RegExp
+      ? search.test(text)
+      : text.indexOf(search) >= 0)
+}
+
+module.exports = {
+  _getTextFromWebElement,
+  _getIndexFromArrayOfWebElements
+}
\ No newline at end of file