Add working e2e-test setup incl. tests

merge-requests/1/head
Florian Hartwich 2017-04-14 03:57:42 +02:00
parent ad73633ae8
commit a964b4b492
15 changed files with 360 additions and 37 deletions

View File

@ -0,0 +1,53 @@
import {browser, element, by} from 'protractor';
import {AbstractHeaderPage} from "../page-object/abstract-header.po";
import {TaskOverviewPage} from "../page-object/task/task-overview.po";
describe('Access Projectmanager Homepage', function () {
beforeEach(() => {
browser.get('/')
});
it('should start at Dashboard page and brand link should redirect there again', () => {
const startingPage = 'Dashboard';
function validatePage() {
const dashboardNavEntryCssClass = element(by.linkText(startingPage)).element(by.xpath('..')).getAttribute('class');
expect(dashboardNavEntryCssClass).toContain('active');
const heading = element(by.css('h1'));
const headingText = heading.getText();
expect(headingText).toBe(startingPage);
}
validatePage();
const navBarBrand = element(by.className('navbar-brand'));
expect(navBarBrand.getAttribute('href')).toBe(browser.baseUrl + '/#');
navBarBrand.click();
validatePage();
});
it('should provide navigation to "Backlog" via Schnellzugriff button - PageObject', () => {
let headerPage = new AbstractHeaderPage;
headerPage.clickSchnellzugriffFollowedBy('Backlog');
headerPage.validateCurrentUrl('tasks?query=BACKLOG');
headerPage.validatePageHeadline('Aufgaben durchsuchen');
});
it('should provide navigation to "Backlog" via Schnellzugriff button - one PageObject per page', () => {
let headerPage = new AbstractHeaderPage;
headerPage.clickSchnellzugriffFollowedBy('Backlog');
const taskOverviewPage = new TaskOverviewPage;
taskOverviewPage.validateCurrentUrl('tasks?query=BACKLOG');
taskOverviewPage.validateSearchFieldValue('BACKLOG');
});
it('should provide navigation to "In Bearbeitung" via Schnellzugriff button', () => {
let headerPage = new AbstractHeaderPage;
headerPage.clickSchnellzugriffFollowedBy('In Bearbeitung');
const taskOverviewPage = new TaskOverviewPage;
taskOverviewPage.validateCurrentUrl('tasks?query=IN_PROGRESS');
taskOverviewPage.validateSearchFieldValue('IN_PROGRESS');
});
});

View File

@ -1,19 +0,0 @@
import {browser, element, by} from 'protractor'
describe('angularjs homepage todo list', function() {
it('should add a todo', function() {
browser.get('https://angularjs.org');
element(by.model('todoList.todoText')).sendKeys('write first protractor test');
element(by.css('[value="add"]')).click();
let todoList = element.all(by.repeater('todo in todoList.todos'));
expect(todoList.count()).toEqual(3);
expect(todoList.get(2).getText()).toEqual('write first protractor test');
// You wrote your first test, cross it off the list
todoList.get(2).element(by.css('input')).click();
let completedAmount = element.all(by.css('.done-true'));
expect(completedAmount.count()).toEqual(2);
});
});

View File

@ -1,9 +0,0 @@
import {browser, element, by} from 'protractor'
describe('Login', function() {
it('should be redirected after login', function() {
browser.get('/');
});
});

View File

@ -0,0 +1,23 @@
import {element, by} from "protractor";
import {AbstractPage} from "./abstract-page.po";
export class AbstractHeaderPage extends AbstractPage {
validatePageHeadline(headline: string) {
const head = element(by.css('h1'));
expect(head.getText()).toBe(headline);
}
clickBrandLink(linkText: string) {
const headerEntry = element(by.linkText(linkText));
headerEntry.click()
}
clickSchnellzugriffFollowedBy(linkText: string) {
// starts with css Selector: https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
const schnellzugriffBtn = element(by.className('dropdown'));
schnellzugriffBtn.click();
schnellzugriffBtn.element(by.linkText(linkText)).click();
}
}

View File

@ -0,0 +1,11 @@
import {browser} from "protractor";
export class AbstractPage {
baseUrl: string = browser.baseUrl + '/';
validateCurrentUrl(urlEnding: string) {
expect(browser.getCurrentUrl()).toBe(this.baseUrl + urlEnding);
}
}

View File

@ -0,0 +1,71 @@
import {AbstractHeaderPage} from "../abstract-header.po";
import {by, element} from "protractor";
import {TaskOverviewPage} from "./task-overview.po";
export class TaskEditPage extends AbstractHeaderPage {
errorMessages = {title: 'Titel muss mindestens 5 Zeichen enthalten',
email : 'Bitte geben Sie eine gültige E-Mail Adresse an'};
titleInput = element(by.id('title'));
descriptionInput = element(by.id('description'));
statusDropdown = element(by.id('state'));
assigneeName = element(by.id('assignee_name'));
assigneeEmail = element(by.id('assignee_email'));
saveButton = element(by.id('save'));
constructor(newTask: boolean) {
super();
let headline;
if (newTask) {
headline = 'Neue Aufgabe anlegen';
} else {
headline = 'Aufgabe bearbeiten';
}
super.validatePageHeadline(headline)
}
clearEnterTitle(title: string) {
this.titleInput.clear();
this.titleInput.sendKeys(title);
}
clearEnterDescription(description: string) {
this.descriptionInput.clear();
this.descriptionInput.sendKeys(description);
}
setStatus(state: string) {
this.statusDropdown.element(by.css('[value="${state}"]'))
.click()// -> Höller, Christoph - Angular, S. 562 - Listing 14.12
}
clearInsertName(name: string) {
this.assigneeName.clear();
this.assigneeName.sendKeys(name);
}
clearInsertEmail(email: string) {
this.assigneeEmail.clear();
this.assigneeEmail.sendKeys(email);
}
submitTaskForm() {
this.saveButton.click();
return new TaskOverviewPage;
}
validateInput(elementId: string, value: string) {
const input = element(by.id(elementId));
expect(input.getAttribute('value')).toBe(value);
}
validateError(input: string) {
expect(element(by.className('alert')).getText())
.toBe(this.errorMessages[input]);
}
}

View File

@ -0,0 +1,37 @@
import {AbstractHeaderPage} from "../abstract-header.po";
import {browser, by, element, ElementArrayFinder, ElementFinder, ExpectedConditions} from "protractor";
import {TaskEditPage} from "./task-edit.po";
export class TaskOverviewPage extends AbstractHeaderPage {
neueAufgabeBtn: ElementFinder;
taskEntries: ElementArrayFinder;
constructor() {
super();
super.validatePageHeadline('Aufgaben durchsuchen');
this.neueAufgabeBtn = element(by.linkText('Neue Aufgabe anlegen'));
this.taskEntries = element.all(by.className('task-list-entry'));
}
validateSearchFieldValue(value: string) {
const inputField = element(by.id('search-tasks'));
expect(inputField.getAttribute('value')).toEqual(value);
}
clickNeueAufgabe(): TaskEditPage {
this.neueAufgabeBtn.click();
return new TaskEditPage(true);
}
verifyNewTask(title: string) {
const newEntry = element(by.linkText(title));
browser.wait(ExpectedConditions.presenceOf(newEntry));
}
clickTask(title: string) {
const entry = element(by.linkText(title));
entry.click();
return new TaskEditPage(false);
}
}

View File

@ -0,0 +1,40 @@
import {browser} from 'protractor';
import {TaskOverviewPage} from "../page-object/task/task-overview.po";
import {TaskEditPage} from "../page-object/task/task-edit.po";
describe('Create New Task Form', function () {
it('should redirect to "New Task Form" and contain new entry in list after creation with valid title and assignee-email', () => {
const testTitle = 'valid title';
const testEmail = 'testuser@test.com';
browser.get('/tasks');
const taskOverviewPage = new TaskOverviewPage;
taskOverviewPage.validateSearchFieldValue('');
const taskEditPage = taskOverviewPage.clickNeueAufgabe();
taskEditPage.clearEnterTitle(testTitle);
taskEditPage.clearInsertEmail(testEmail);
taskEditPage.submitTaskForm().verifyNewTask(testTitle);
});
it('should show error-message on title entered being too short', () => {
const testTitle = 'xyz';
browser.get('/tasks/new');
const taskEditPage = new TaskEditPage(true);
taskEditPage.clearEnterTitle(testTitle);
taskEditPage.clearEnterDescription('');
taskEditPage.validateError('title');
});
it('should show error-message on email entered not matching pattern', () => {
const testEmail = 'wrong@mail.';
browser.get('/tasks/new');
const taskEditPage = new TaskEditPage(true);
taskEditPage.clearInsertEmail(testEmail);
taskEditPage.clearEnterDescription('');
taskEditPage.validateError('email');
})
});

View File

@ -0,0 +1,45 @@
import {browser} from 'protractor';
import {TaskOverviewPage} from "../page-object/task/task-overview.po";
import {TaskEditPage} from "../page-object/task/task-edit.po";
describe('Edit Task Form', function () {
const dbTask = {
id: 3,
title: "Ersten Prototyp mit Angular 2.0 entwickeln",
description: "Der Prototyp soll zeigen, wie Routing und HTTP-Anbindung umgesetzt werden können.",
tags: [],
state: "IN_PROGRESS",
assignee: {
name: "Christoph Höller",
email: ""
}
};
it('should load task with its values in edit form', () => {
browser.get('/tasks');
const taskOverviewPage = new TaskOverviewPage;
taskOverviewPage.validateSearchFieldValue('');
const taskEditPage = taskOverviewPage.clickTask(dbTask.title);
// check for correct url - test input field values
taskEditPage.validateCurrentUrl('tasks/edit/' + dbTask.id);
taskEditPage.validateInput('title', dbTask.title);
taskEditPage.validateInput('description', dbTask.description);
taskEditPage.validateInput('state', dbTask.state);
taskEditPage.validateInput('assignee_name', dbTask.assignee['name'])
taskEditPage.validateInput('assignee_email', dbTask.assignee['email'])
});
it('should have updated title in overview after edit, but same task id as before', () => {
browser.get('/tasks/edit/' + dbTask.id);
const newTitle = 'Ersten Prototyp mit Angular 4.0 entwickelt';
const taskEditPage = new TaskEditPage(false);
taskEditPage.clearEnterTitle(newTitle);
let taskOverviewPage = taskEditPage.submitTaskForm();
// validate that ID is kept after change
taskOverviewPage.verifyNewTask(newTitle);
taskOverviewPage.clickTask(newTitle).validateCurrentUrl('tasks/edit/' + dbTask.id);
});
});

View File

@ -3,8 +3,8 @@
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types":[
"target": "es6",
"types": [
"jasmine",
"node"
]

View File

@ -9,8 +9,8 @@
"starte2e": "concurrently \"node projects-server/server.js db=projects-server/test.json\" \"ng serve --env=e2e\" ",
"lint": "tslint \"src/**/*.ts\" --project src/tsconfig.json --type-check && tslint \"e2e/**/*.ts\" --project e2e/tsconfig.json --type-check",
"test": "ng test",
"pree2e": "webdriver-manager update --standalone false --gecko false",
"e2e": "protractor",
"pree2e": "cp ./projects-server/safe/test.json ./projects-server/test.json && webdriver-manager update --standalone false --gecko false",
"e2e": "concurrently \"node projects-server/server.js db=projects-server/test.json\" \"ng e2e --env=e2e\" ",
"install-server": "npm install --prefix ./projects-server ./projects-server",
"e2e-screenshots": "protractor ./protractor-html-reporter.conf.js",
"postinstall": "npm run install-server"

View File

@ -0,0 +1,62 @@
{
"tasks": [
{
"id": 3,
"title": "Ersten Prototyp mit Angular 2.0 entwickeln",
"description": "Der Prototyp soll zeigen, wie Routing und HTTP-Anbindung umgesetzt werden können.",
"tags": [],
"state": "IN_PROGRESS",
"assignee": {
"name": "Christoph Höller",
"email": ""
}
},
{
"id": 4,
"title": "Präsentation vorbereiten...",
"description": "Nicht länger als 15 Minuten... ",
"tags": [],
"assignee": {
"name": "John Doe",
"email": "john@doe.com"
},
"state": "BACKLOG"
},
{
"title": "Test der Websocket-Funktionalität",
"description": "Mit Hilfe der Websocket-Funktionalität soll es möglich sein, die Task-Daten über alle Browserfenster hinweg synchron zu halten.",
"tags": [],
"assignee": {},
"id": 5,
"state": "BACKLOG"
},
{
"assignee": {},
"tags": [],
"state": "IN_PROGRESS",
"title": "New Task 1484898385971",
"id": 6
},
{
"assignee": {},
"tags": [],
"state": "IN_PROGRESS",
"title": "New Task",
"id": 7
},
{
"assignee": {},
"tags": [],
"state": "IN_PROGRESS",
"title": "New Task 1487974284967",
"id": 8
},
{
"assignee": {},
"tags": [],
"state": "IN_PROGRESS",
"title": "New Task",
"id": 9
}
]
}

View File

@ -2,7 +2,7 @@
"tasks": [
{
"id": 3,
"title": "Ersten Prototyp mit Angular 2.0 entwickeln",
"title": "Ersten Prototyp mit Angular 4.0 entwickelt",
"description": "Der Prototyp soll zeigen, wie Routing und HTTP-Anbindung umgesetzt werden können.",
"tags": [],
"state": "IN_PROGRESS",
@ -57,6 +57,15 @@
"state": "IN_PROGRESS",
"title": "New Task",
"id": 9
},
{
"assignee": {
"email": "testuser@test.com"
},
"tags": [],
"state": "BACKLOG",
"title": "valid title",
"id": 10
}
]
}

View File

@ -3,7 +3,7 @@
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
exports.config= {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
@ -12,7 +12,7 @@ exports.config = {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
baseUrl: 'http://localhost:4200',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,

View File

@ -1,5 +1,5 @@
<h2 *ngIf="!task.id">Neue Aufgabe anlegen</h2>
<h2 *ngIf="task.id">Aufgabe bearbeiten</h2>
<h1 *ngIf="!task.id">Neue Aufgabe anlegen</h1>
<h1 *ngIf="task.id">Aufgabe bearbeiten</h1>
<form (submit)="saveTask()" #form="ngForm">
<div class="form-group">