Running End-to-End Tests
~~~~~~~~~~~~~~~~~~~~~~~~
-Run ``npm run e2e`` to execute the end-to-end tests via
-`Protractor <http://www.protractortest.org/>`__.
+We use `Protractor <http://www.protractortest.org/>`__ 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 <chrome|docker>
+
+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 <DASHBOARD_URL>
+
+Note:
+ When using docker, as your device, you might need to run the script with sudo
+ permissions.
Further Help
~~~~~~~~~~~~
+++ /dev/null
-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!');
- });
-});
+++ /dev/null
-import { browser, by, element } from 'protractor';
-
-export class AppPage {
- navigateTo() {
- return browser.get('/');
- }
-
- getParagraphText() {
- return element(by.css('oa-root h1')).getText();
- }
-}
--- /dev/null
+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');
+ });
+});
--- /dev/null
+import { browser } from 'protractor';
+
+export class MonitorsPage {
+ navigateTo() {
+ return browser.get('/#/monitor');
+ }
+}
--- /dev/null
+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');
+ });
+});
--- /dev/null
+import { browser } from 'protractor';
+
+export class OSDsPage {
+ navigateTo() {
+ return browser.get('/#/osd');
+ }
+}
--- /dev/null
+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();
+ }
+}
"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",
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
- 'browserName': 'chrome'
+ 'browserName': 'chrome',
+ chromeOptions: {
+ args: ['--no-sandbox', '--headless', '--window-size=1920x1080']
+ }
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
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);
+ });
+ });
}
};
Component,
EventEmitter,
Input,
+ NgZone,
OnChanges,
OnDestroy,
OnInit,
// table columns after the browser window has been resized.
private currentWidth: number;
- constructor() {}
+ constructor(private ngZone: NgZone) {}
ngOnInit() {
this._addTemplates();
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();
+ });
+ });
});
}
}
import { HttpClient } from '@angular/common/http';
-import { Injectable } from '@angular/core';
+import { Injectable, NgZone } from '@angular/core';
import { Subject } from 'rxjs/Subject';
// 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();
}
});
}
- setTimeout(() => {
- this.refresh();
- }, 5000);
+ this.ngZone.runOutsideAngular(() => {
+ setTimeout(() => {
+ this.ngZone.run(() => {
+ this.refresh();
+ });
+ }, 5000);
+ });
}
}
--- /dev/null
+#!/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