From 99d146c6e57a44068c9885d4c24662f045604efe Mon Sep 17 00:00:00 2001 From: Tiago Melo Date: Fri, 2 Mar 2018 16:39:46 +0000 Subject: [PATCH] mgr/dashboard: fix frontend e2e tests Updated protractor configuration: now it's possible to run the tests inside a docker container, added params for the username and password and added login during the prepare phase. Chrome is required to run the e2e tests. This can be achieved by installing Chrome in your system or by using a docker container with Chrome installed. Changed some timer calls, so they are runned outside of angular. This was preventing protractor from running the tests. Signed-off-by: Tiago Melo --- src/pybind/mgr/dashboard/HACKING.rst | 26 +++++++- .../dashboard/frontend/e2e/app.e2e-spec.ts | 14 ---- .../mgr/dashboard/frontend/e2e/app.po.ts | 11 ---- .../frontend/e2e/cluster/monitors.e2e-spec.ts | 19 ++++++ .../frontend/e2e/cluster/monitors.po.ts | 7 ++ .../frontend/e2e/cluster/osds.e2e-spec.ts | 19 ++++++ .../dashboard/frontend/e2e/cluster/osds.po.ts | 7 ++ .../mgr/dashboard/frontend/e2e/helper.po.ts | 34 ++++++++++ .../mgr/dashboard/frontend/package.json | 2 +- .../mgr/dashboard/frontend/protractor.conf.js | 29 +++++++- .../shared/datatable/table/table.component.ts | 14 ++-- .../app/shared/services/summary.service.ts | 18 +++-- .../mgr/dashboard/run-frontend-e2e-tests.sh | 66 +++++++++++++++++++ 13 files changed, 228 insertions(+), 38 deletions(-) delete mode 100644 src/pybind/mgr/dashboard/frontend/e2e/app.e2e-spec.ts delete mode 100644 src/pybind/mgr/dashboard/frontend/e2e/app.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/e2e/helper.po.ts create mode 100755 src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh diff --git a/src/pybind/mgr/dashboard/HACKING.rst b/src/pybind/mgr/dashboard/HACKING.rst index d5764f90a9d5..b17c17b2deeb 100644 --- a/src/pybind/mgr/dashboard/HACKING.rst +++ b/src/pybind/mgr/dashboard/HACKING.rst @@ -72,8 +72,30 @@ Run ``npm run test`` to execute the unit tests via `Karma Running End-to-End Tests ~~~~~~~~~~~~~~~~~~~~~~~~ -Run ``npm run e2e`` to execute the end-to-end tests via -`Protractor `__. +We use `Protractor `__ to run our frontend e2e +tests. + +Our ``run-frontend-e2e-tests.sh`` script will check if Chrome or Docker is +installed and run the tests if either is found. + +Start all frontend e2e tests by running:: + + $ ./run-frontend-e2e-tests.sh + +Device: + You can force the script to use a specific device with the ``-d`` flag:: + + $ ./run-frontend-e2e-tests.sh -d + +Remote: + If you want to run the tests outside the ceph environment, you will need to + manually define the dashboard url using ``-r``:: + + $ ./run-frontend-e2e-tests.sh -r + +Note: + When using docker, as your device, you might need to run the script with sudo + permissions. Further Help ~~~~~~~~~~~~ diff --git a/src/pybind/mgr/dashboard/frontend/e2e/app.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/app.e2e-spec.ts deleted file mode 100644 index 3e9837024233..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/app.e2e-spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AppPage } from './app.po'; - -describe('ceph-dashboard App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display welcome message', () => { - page.navigateTo(); - expect(page.getParagraphText()).toEqual('Welcome to oa!'); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/app.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/app.po.ts deleted file mode 100644 index d9761bb4a4e4..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/app.po.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { browser, by, element } from 'protractor'; - -export class AppPage { - navigateTo() { - return browser.get('/'); - } - - getParagraphText() { - return element(by.css('oa-root h1')).getText(); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.e2e-spec.ts new file mode 100644 index 000000000000..31eafef5c9b4 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.e2e-spec.ts @@ -0,0 +1,19 @@ +import { Helper } from '../helper.po'; +import { MonitorsPage } from './monitors.po'; + +describe('Monitors page', () => { + let page: MonitorsPage; + + beforeAll(() => { + page = new MonitorsPage(); + }); + + afterEach(() => { + Helper.checkConsole(); + }); + + it('should open and show breadcrumnb', () => { + page.navigateTo(); + expect(Helper.getBreadcrumbText()).toEqual('Monitors'); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.po.ts new file mode 100644 index 000000000000..53159d30e4d5 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.po.ts @@ -0,0 +1,7 @@ +import { browser } from 'protractor'; + +export class MonitorsPage { + navigateTo() { + return browser.get('/#/monitor'); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts new file mode 100644 index 000000000000..edccc90a8ee6 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts @@ -0,0 +1,19 @@ +import { Helper } from '../helper.po'; +import { OSDsPage } from './osds.po'; + +describe('OSDs page', () => { + let page: OSDsPage; + + beforeAll(() => { + page = new OSDsPage(); + }); + + afterEach(() => { + Helper.checkConsole(); + }); + + it('should open and show breadcrumnb', () => { + page.navigateTo(); + expect(Helper.getBreadcrumbText()).toEqual('OSDs'); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.po.ts new file mode 100644 index 000000000000..08e4143831fe --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.po.ts @@ -0,0 +1,7 @@ +import { browser } from 'protractor'; + +export class OSDsPage { + navigateTo() { + return browser.get('/#/osd'); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/helper.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/helper.po.ts new file mode 100644 index 000000000000..a086595a5be2 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/e2e/helper.po.ts @@ -0,0 +1,34 @@ +import { $, browser } from 'protractor'; + +export class Helper { + static EC = browser.ExpectedConditions; + static TIMEOUT = 10000; + + /** + * Checks if there are any errors on the browser + * + * @static + * @memberof Helper + */ + static checkConsole() { + browser + .manage() + .logs() + .get('browser') + .then(function(browserLog) { + browserLog = browserLog.filter(log => { + return log.level.value > 900; // SEVERE level + }); + + if (browserLog.length > 0) { + console.log('\n log: ' + require('util').inspect(browserLog)); + } + + expect(browserLog.length).toEqual(0); + }); + } + + static getBreadcrumbText() { + return $('.breadcrumb-item.active').getText(); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/package.json b/src/pybind/mgr/dashboard/frontend/package.json index 6ca56e1b12e2..1a4936edf6fd 100644 --- a/src/pybind/mgr/dashboard/frontend/package.json +++ b/src/pybind/mgr/dashboard/frontend/package.json @@ -55,7 +55,7 @@ "karma-jasmine-html-reporter": "^0.2.2", "karma-junit-reporter": "^1.2.0", "karma-phantomjs-launcher": "^1.0.4", - "protractor": "~5.1.2", + "protractor": "~5.3.0", "ts-node": "~3.2.0", "tslint": "~5.9.1", "tslint-eslint-rules": "^4.1.1", diff --git a/src/pybind/mgr/dashboard/frontend/protractor.conf.js b/src/pybind/mgr/dashboard/frontend/protractor.conf.js index 7ee3b5ee863a..ae047de8bb54 100644 --- a/src/pybind/mgr/dashboard/frontend/protractor.conf.js +++ b/src/pybind/mgr/dashboard/frontend/protractor.conf.js @@ -9,7 +9,10 @@ exports.config = { './e2e/**/*.e2e-spec.ts' ], capabilities: { - 'browserName': 'chrome' + 'browserName': 'chrome', + chromeOptions: { + args: ['--no-sandbox', '--headless', '--window-size=1920x1080'] + } }, directConnect: true, baseUrl: 'http://localhost:4200/', @@ -19,10 +22,34 @@ exports.config = { defaultTimeoutInterval: 30000, print: function() {} }, + params: { + login: { + user: 'admin', + password: 'admin' + } + }, onPrepare() { require('ts-node').register({ project: 'e2e/tsconfig.e2e.json' }); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + + browser.get('/#/login'); + + browser.driver.findElement(by.name('username')).clear(); + browser.driver.findElement(by.name('username')).sendKeys(browser.params.login.user); + browser.driver.findElement(by.name('password')).clear(); + browser.driver.findElement(by.name('password')).sendKeys(browser.params.login.password); + + browser.driver.findElement(by.css('input[type="submit"]')).click(); + + // Login takes some time, so wait until it's done. + // For the test app's login, we know it's done when it redirects to + // dashboard. + return browser.driver.wait(function() { + return browser.driver.getCurrentUrl().then(function(url) { + return /dashboard/.test(url); + }); + }); } }; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts index 9f04e91c71ca..2db184dcd5a9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts @@ -3,6 +3,7 @@ import { Component, EventEmitter, Input, + NgZone, OnChanges, OnDestroy, OnInit, @@ -113,7 +114,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O // table columns after the browser window has been resized. private currentWidth: number; - constructor() {} + constructor(private ngZone: NgZone) {} ngOnInit() { this._addTemplates(); @@ -136,12 +137,17 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O return c; }); this.tableColumns = this.columns.filter(c => !c.isHidden); - if (this.autoReload) { // Also if nothing is bound to fetchData nothing will be triggered + if (this.autoReload) { + // Also if nothing is bound to fetchData nothing will be triggered // Force showing the loading indicator because it has been set to False in // useData() when this method was triggered by ngOnChanges(). this.loadingIndicator = true; - this.subscriber = Observable.timer(0, this.autoReload).subscribe(x => { - return this.reloadData(); + this.ngZone.runOutsideAngular(() => { + this.subscriber = Observable.timer(0, this.autoReload).subscribe(x => { + this.ngZone.run(() => { + return this.reloadData(); + }); + }); }); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts index 9556930ebd42..5709a77125f3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts @@ -1,5 +1,5 @@ import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { Subject } from 'rxjs/Subject'; @@ -13,7 +13,11 @@ export class SummaryService { // Observable streams summaryData$ = this.summaryDataSource.asObservable(); - constructor(private http: HttpClient, private authStorageService: AuthStorageService) { + constructor( + private http: HttpClient, + private authStorageService: AuthStorageService, + private ngZone: NgZone + ) { this.refresh(); } @@ -24,8 +28,12 @@ export class SummaryService { }); } - setTimeout(() => { - this.refresh(); - }, 5000); + this.ngZone.runOutsideAngular(() => { + setTimeout(() => { + this.ngZone.run(() => { + this.refresh(); + }); + }, 5000); + }); } } diff --git a/src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh b/src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh new file mode 100755 index 000000000000..b831a33fa713 --- /dev/null +++ b/src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +set -e + +stop() { + if [ "$REMOTE" == "false" ]; then + cd $BUILD_DIR + ../src/stop.sh + fi + exit +} + +BASE_URL='' +DEVICE='' +REMOTE='false' + +while getopts 'd:r:' flag; do + case "${flag}" in + d) DEVICE=$OPTARG;; + r) REMOTE='true' + BASE_URL=$OPTARG;; + esac +done + +if [ "$DEVICE" == "" ]; then + if [ -x "$(command -v google-chrome)" ] || [ -x "$(command -v google-chrome-stable)" ]; then + DEVICE="chrome" + elif [ -x "$(command -v docker)" ]; then + DEVICE="docker" + else + echo "ERROR: Chrome and Docker not found. You need to install one of \ +them to run the e2e frontend tests." + stop + fi +fi + +DASH_DIR=`pwd` + +cd ../../../../build +BUILD_DIR=`pwd` + +if [ "$BASE_URL" == "" ]; then + MGR=2 RGW=1 ../src/vstart.sh -n -d + sleep 10 + + BASE_URL=`./bin/ceph mgr services | jq .dashboard` + BASE_URL=${BASE_URL//\"} +fi + +cd $DASH_DIR/frontend +. $BUILD_DIR/src/pybind/mgr/dashboard/node-env/bin/activate +npm install +npm run build -- --prod + +if [ $DEVICE == "chrome" ]; then + npm run e2e -- --serve=false --base-href $BASE_URL || stop +elif [ $DEVICE == "docker" ]; then + docker run -d -v $(pwd):/workdir --net=host --name angular-e2e-container rogargon/angular-e2e || stop + docker exec angular-e2e-container npm run e2e -- --serve=false --base-href $BASE_URL + docker stop angular-e2e-container + docker rm angular-e2e-container +else + echo "ERROR: Device not recognized. Valid devices are 'chrome' and 'docker'." +fi + +stop -- 2.47.3