From 8cbe8a7f3980536aad74890d6f23d0218c95077d Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Thu, 13 Feb 2020 13:58:02 +0100 Subject: [PATCH] bugfix: https://github.com/HumanBrainProject/interactive-viewer/issues/475 chore: added test to prevent regression --- e2e/chromeOpts.js | 2 +- e2e/src/selecting/template.e2e-spec.js | 41 ++++++ e2e/src/util.js | 138 +++++++++++++++++- package.json | 4 +- src/services/logging.service.ts | 6 +- .../nehubaContainer.component.ts | 5 +- .../nehubaViewer/nehubaViewer.component.ts | 5 +- .../viewerState.template.html | 1 + 8 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 e2e/src/selecting/template.e2e-spec.js diff --git a/e2e/chromeOpts.js b/e2e/chromeOpts.js index fc42e95cc..b6900fed9 100644 --- a/e2e/chromeOpts.js +++ b/e2e/chromeOpts.js @@ -1,5 +1,5 @@ module.exports = [ - '--headless', + ...(process.env.DISABLE_CHROME_HEADLESS ? [] : ['--headless']), '--no-sandbox', '--disable-gpu', '--disable-setuid-sandbox', diff --git a/e2e/src/selecting/template.e2e-spec.js b/e2e/src/selecting/template.e2e-spec.js new file mode 100644 index 000000000..55baf251a --- /dev/null +++ b/e2e/src/selecting/template.e2e-spec.js @@ -0,0 +1,41 @@ +const { AtlasPage } = require("../util") + +describe('selecting template', () => { + let iavPage + + beforeEach(async () => { + iavPage = new AtlasPage() + await iavPage.init() + }) + + 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) + + const viewerIsPopulated = await iavPage.viewerIsPopulated() + expect(viewerIsPopulated).toBeTrue + }) + + + 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) + + await iavPage.selectDropdownTemplate('Big Brain (Histology)') + await iavPage.wait(7000) + + const viewerIsPopulated = await iavPage.viewerIsPopulated() + expect(viewerIsPopulated).toBeTrue + }) +}) diff --git a/e2e/src/util.js b/e2e/src/util.js index 149e3edc2..bcf8e0a2f 100644 --- a/e2e/src/util.js +++ b/e2e/src/util.js @@ -1,3 +1,137 @@ +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 } = require('selenium-webdriver') + +function getActualUrl(url) { + return /^http\:\/\//.test(url) ? url : `${ATLAS_URL}/${url.replace(/^\//, '')}` +} + +async function getIndexFromArrayOfWebElements(search, webElements) { + const texts = await Promise.all( + webElements.map(e => e.getText()) + ) + return texts.findIndex(text => text.indexOf(search) >= 0) +} + +class WdIavPage{ + constructor(){ + + } + + async init() { + + } + + async goto(url = '/'){ + const actualUrl = getActualUrl(url) + await browser.get(actualUrl) + } + + async wait(ms) { + if (!ms) throw new Error(`wait duration must be specified!`) + await browser.sleep(ms) + } + + async getModal() { + return await browser.findElement( By.tagName('mat-dialog-container') ) + } + + async dismissModal() { + try { + const modal = await this.getModal() + const okBtn = await modal + .findElement( By.tagName('mat-dialog-actions') ) + .findElement( By.css('button[color="primary"] > span.mat-button-wrapper') ) + await okBtn.click() + } catch (e) { + console.log(e) + } + } + + async findTitleCards() { + return await browser + .findElement( By.tagName('ui-splashscreen') ) + .findElements( By.tagName('mat-card') ) + } + + async selectTitleCard( title ) { + const titleCards = await this.findTitleCards() + const idx = await getIndexFromArrayOfWebElements(title, titleCards) + if (idx >= 0) await titleCards[idx].click() + else throw new Error(`${title} does not fit any titleCards`) + } + + async selectDropdownTemplate(title) { + const templateBtn = await (await this.getSideNav()) + .findElement( By.tagName('viewer-state-controller') ) + .findElement( By.css('[aria-label="Select a new template"]') ) + await templateBtn.click() + + const options = await browser.findElements( + By.tagName('mat-option') + ) + const idx = await getIndexFromArrayOfWebElements(title, options) + if (idx >= 0) await options[idx].click() + else throw new Error(`${title} is not found as one of the dropdown templates`) + } + + async screenshotViewer() { + const ngContainer = await this.getViewerContainer() + if (!ngContainer) throw new Error(`neuroglancer-container not defined`) + return await ngContainer.takeScreenshot(false) + } + + async getViewerContainer() { + return await browser.findElement( + By.id('neuroglancer-container') + ) + } + + async viewerIsPopulated() { + const ngContainer = await this.getViewerContainer() + const canvas = await ngContainer.findElement( + By.tagName('canvas') + ) + return !!canvas + } + + async getSideNav() { + return await browser.findElement( By.tagName('search-side-nav') ) + } +} + +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.getSearchParam = page => { return page.evaluate(`window.location.search`) } @@ -12,4 +146,6 @@ exports.wait = (browser) => new Promise(resolve => { .then(resolve) }) -exports.waitMultiple = process.env.WAIT_ULTIPLE || 1 \ No newline at end of file +exports.waitMultiple = process.env.WAIT_ULTIPLE || 1 + +exports.AtlasPage = WdIavPage diff --git a/package.json b/package.json index ccc3cf586..f0352b0a2 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "plugin-server": "node ./src/plugin_examples/server.js", "dev-server": "BACKEND_URL=${BACKEND_URL:-http://localhost:3000/} webpack-dev-server --config webpack.dev.js --mode development", "dev": "npm run dev-server & (cd deploy; node server.js)", - "dev-server-aot": "PRODUCTION=true GIT_HASH=`git log --pretty=format:'%h' --invert-grep --grep=^.ignore -1` webpack-dev-server --config webpack.aot.js", + "dev-server-aot": "BACKEND_URL=${BACKEND_URL:-http://localhost:3000/} PRODUCTION=true GIT_HASH=`git log --pretty=format:'%h' --invert-grep --grep=^.ignore -1` webpack-dev-server --config webpack.aot.js", "dev-server-all-interfaces": "webpack-dev-server --config webpack.dev.js --mode development --hot --host 0.0.0.0", "test": "karma start spec/karma.conf.js", "e2e": "protractor e2e/protractor.conf", @@ -21,7 +21,7 @@ }, "keywords": [], "author": "", - "license": "ISC", + "license": "TO BE DECIDED", "devDependencies": { "@angular/animations": "^9.0.0", "@angular/cdk": "^9.0.0", diff --git a/src/services/logging.service.ts b/src/services/logging.service.ts index 718b96a71..0f4d879e2 100644 --- a/src/services/logging.service.ts +++ b/src/services/logging.service.ts @@ -7,7 +7,11 @@ import { Injectable } from "@angular/core"; }) export class LoggingService { - private loggingFlag: boolean = !PRODUCTION + + get loggingFlag(){ + return window['__IAV_LOGGING_FLAG__'] || !PRODUCTION + } + public log(...arg) { if (this.loggingFlag) { console.log(...arg) } } diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 15ab15977..694ff1e98 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Input, OnChanges, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; +import { Component, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Input, OnChanges, OnDestroy, OnInit, ViewChild, ViewContainerRef, ChangeDetectorRef } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { combineLatest, fromEvent, merge, Observable, of, Subscription, from } from "rxjs"; import { pipeFromArray } from "rxjs/internal/util/pipe"; @@ -153,6 +153,7 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { private store: Store<ViewerStateInterface>, private elementRef: ElementRef, private log: LoggingService, + private cdr: ChangeDetectorRef ) { this.useMobileUI$ = this.constantService.useMobileUI$ @@ -984,6 +985,8 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy { this.viewerLoaded = false this.nehubaViewer = null + + this.cdr.detectChanges() } private createNewNehuba(template: any, overwriteInitNavigation: any) { diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts index 4b1b28cc7..09b8cd4ed 100644 --- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts +++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, EventEmitter, NgZone, OnDestroy, OnInit, Output, Renderer2 } from "@angular/core"; +import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, Renderer2 } from "@angular/core"; import { fromEvent, Subscription, ReplaySubject } from 'rxjs' import { pipeFromArray } from "rxjs/internal/util/pipe"; import { debounceTime, filter, map, scan } from "rxjs/operators"; @@ -118,7 +118,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { private rd: Renderer2, public elementRef: ElementRef, private workerService: AtlasWorkerService, - private zone: NgZone, public constantService: AtlasViewerConstantsServices, private log: LoggingService, ) { @@ -341,7 +340,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { public loadNehuba() { this.nehubaViewer = this.exportNehuba.createNehubaViewer(this.config, (err) => { /* print in debug mode */ - this.log.log(err) + this.log.error(err) }) /** diff --git a/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html b/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html index 32675891b..3da00c0c9 100644 --- a/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html +++ b/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html @@ -9,6 +9,7 @@ Template </mat-label> <mat-select + aria-label="Select a new template" panelClass="no-max-width" [value]="(templateSelected$ | async)?.name" (selectionChange)="handleTemplateChange($event)" -- GitLab