]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fix frontend e2e tests 20943/head
authorTiago Melo <tmelo@suse.com>
Fri, 2 Mar 2018 16:39:46 +0000 (16:39 +0000)
committerTiago Melo <tmelo@suse.com>
Fri, 6 Apr 2018 09:33:34 +0000 (10:33 +0100)
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 <tmelo@suse.com>
13 files changed:
src/pybind/mgr/dashboard/HACKING.rst
src/pybind/mgr/dashboard/frontend/e2e/app.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/e2e/app.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/e2e/helper.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/package.json
src/pybind/mgr/dashboard/frontend/protractor.conf.js
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts
src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh [new file with mode: 0755]

index d5764f90a9d5d39d2ac615924c559a5af89ecb78..b17c17b2deeb5fd87a9fe9647f5fa33c91a740d4 100644 (file)
@@ -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 <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
 ~~~~~~~~~~~~
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 (file)
index 3e98370..0000000
+++ /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 (file)
index d9761bb..0000000
+++ /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 (file)
index 0000000..31eafef
--- /dev/null
@@ -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 (file)
index 0000000..53159d3
--- /dev/null
@@ -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 (file)
index 0000000..edccc90
--- /dev/null
@@ -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 (file)
index 0000000..08e4143
--- /dev/null
@@ -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 (file)
index 0000000..a086595
--- /dev/null
@@ -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();
+  }
+}
index 6ca56e1b12e247e5be4bb0529d9feaab42784f62..1a4936edf6fdb0c5038733157c3d8b7ed7eff7f1 100644 (file)
@@ -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",
index 7ee3b5ee863a74c87dbc4f4bee10b1fd1016cbf4..ae047de8bb5419612bae545ab33a17410a19d7d3 100644 (file)
@@ -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);
+      });
+    });
   }
 };
index 9f04e91c71ca748e96fda8003ed54f9243b3aa87..2db184dcd5a992b36348c999faf1e1e0ef368833 100644 (file)
@@ -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();
+          });
+        });
       });
     }
   }
index 9556930ebd4223da21035a618915766afe43123a..5709a77125f34a990c3f4b982ff445bd752818ed 100644 (file)
@@ -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 (executable)
index 0000000..b831a33
--- /dev/null
@@ -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