Skip to content

Commit

Permalink
add protractor tests to contact-app-starter
Browse files Browse the repository at this point in the history
- add back `id="add-contact"` to the contact-list template
- add Protractor tests: contact list, new contact, and with page object
  tests
- add circle ci integration
- update .gitignore to not save output/ directory for junit tests
- add circleci shield to readme file

closes testing-angular-applications#14
  • Loading branch information
cnishina committed Sep 20, 2017
1 parent f390f67 commit 6367383
Show file tree
Hide file tree
Showing 16 changed files with 529 additions and 33 deletions.
17 changes: 17 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: 2
jobs:
build:
working_directory: ~/workspace
docker:
- image: circleci/node:7-browsers
steps:
- checkout

- run: export IS_CIRCLE=true
- run: node --version
- run: npm --version
- run: yarn --version
- run: google-chrome --version

- run: yarn install
- run: yarn run ng e2e protractor.conf.js
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
/coverage
/libpeerconnection.log
npm-debug.log
/output/
testem.log
/typings
yarn-error.log
Expand All @@ -44,7 +45,6 @@ yarn-error.log
# e2e
/e2e/*.js
/e2e/*.map
/e2e/*-spec.ts

# unit tests
*.spec.ts
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
[![Code Climate](https://codeclimate.com/github/testing-angular-applications/contacts-app-starter/badges/gpa.svg)](https://codeclimate.com/github/testing-angular-applications/contacts-app-starter)
[![Dependencies Status](https://david-dm.org/testing-angular-applications/contacts-app-starter/status.svg)](https://david-dm.org/testing-angular-applications/contacts-app-starter)
[![devDependencies Status](https://david-dm.org/testing-angular-applications/contacts-app-starter/dev-status.svg)](https://david-dm.org/testing-angular-applications/contacts-app-starter?type=dev)
[![CircleCI Status](https://circleci.com/gh/testing-angular-applications/contacts-app-starter.svg?style=svg)](https://circleci.com/gh/testing-angular-applications/contacts-app-starter)

# Contacts App Starter

Expand Down
11 changes: 0 additions & 11 deletions e2e/app.po.ts

This file was deleted.

108 changes: 108 additions & 0 deletions e2e/contact-list.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { browser, by, element, ElementFinder } from 'protractor';
import { promise as wdpromise } from 'selenium-webdriver';

export interface Contact {
name?: string;
email?: string;
tel?: string;
}

describe('the contact list', () => {
beforeAll(() => {
browser.get('/');
});

it('with filter: should find existing contact "Craig Service"', () => {
let tbody = element(by.tagName('tbody'));
let trs = tbody.all(by.tagName('tr'));
let craigService = trs.filter(elem => {
// The tds: 0 = mood, 1 = name, 2 = email, 3 = phone number
return elem.all(by.tagName('td')).get(1).getText().then(text => {
return text === 'Craig Service';
});
});
// Nothing happens here until you use it. Although we called getText in the filter function,
// it is not executed until we use it. When you use it, the promises enter the control flow
// and are resolved. This is similar to calling element(), nothing happens until you do
// something like getText().
expect(craigService.count()).toBeGreaterThan(0);
expect(craigService.all(by.tagName('td')).get(2).getText())
.toEqual('craig.services@example.com');
});

let expectedContactList: Contact[] = [{
name: 'Adrian Directive',
email: 'adrian.directive@example.com',
tel: '+1 (703) 555-0123'
}, {
name: 'Rusty Component',
email: 'rusty.component@example.com',
tel: '+1 (441) 555-0122'
}, {
name: 'Jeff Pipe',
email: 'jeff.pipe@example.com',
tel: '+1 (714) 555-0111'
}, {
name: 'Craig Service',
email: 'craig.services@example.com',
tel: '+1 (514) 555-0132'
}];

it('with map: should create a map object', () => {
let tbody = element(by.tagName('tbody'));
let trs = tbody.all(by.tagName('tr'));
let contactList = trs.map(elem => {
let contact: Contact = {};
let tds = elem.all(by.tagName('td'));
// We need to get the values of the contact name and email. Since these are in a couple of
// different promises, we'll create a promise array.
let promises: any[] = [];

// Getting the text returns a promise of a string then the next function sets the contact's
// name. This function returns void so the final promise saved is of Promise<void>.
// We set the promise array to be of type any since we do not care about the promise type.
promises.push(tds.get(1).getText().then(text=> {
contact.name = text;
}));
promises.push(tds.get(2).getText().then(text => {
contact.email = text;
}));
promises.push(tds.get(3).getText().then(text => {
contact.tel = text;
}));

// Resolve all the promises and return the contact.
return Promise.all(promises).then(() => {
return contact;
});
})

// Check the results
expect(contactList).toBeDefined();
contactList.then((contacts: Contact[]) => {

// Spot check the results
expect(contacts.length).toEqual(4);
expect(contacts[0]).toBeDefined();
expect(contacts[1].email).toEqual('rusty.component@example.com');
expect(contacts[2].tel).toEqual('+1 (714) 555-0111');
expect(contacts[3].name).toEqual('Craig Service');

// Check all the contacts match
expect(contacts).toEqual(expectedContactList);
});
});

it('with reduce: get a list of contact names', () => {
let tbody = element(by.tagName('tbody'));
let trs = tbody.all(by.tagName('tr'));
let contacts = trs.reduce((acc, curr) => {
let name = curr.all(by.tagName('td')).get(1);
return name.getText().then(text => {
return acc === '' ? text : acc + ', ' + text;
});
}, '');

expect(contacts).toBe('Adrian Directive, Rusty Component, Jeff Pipe, Craig Service');
});
});
8 changes: 8 additions & 0 deletions e2e/first-test.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { browser } from 'protractor';

describe('our first Protractor test', () => {
it('should load a page and verify the url', () => {
browser.get('/');
expect(browser.getCurrentUrl()).toEqual(browser.baseUrl + '/');
});
});
110 changes: 110 additions & 0 deletions e2e/new-contact.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {browser, by, element, ExpectedConditions as EC} from 'protractor';

describe('interact with elements', () => {

describe('for a new valid user', () => {
beforeAll(() => {
browser.get('/');
});

it('should find the add contact button', () => {
element(by.id('add-contact')).click();
expect(browser.getCurrentUrl()).toEqual(browser.baseUrl + '/add');
});

it('should write a name', () => {
element(by.id('contact-name')).sendKeys('Ada');
expect(element(by.id('contact-name')).getAttribute('value')).toEqual('Ada');
});

it('should click the create button', () => {
element(by.css('.create-button')).click();
expect(browser.getCurrentUrl()).toEqual(browser.baseUrl + '/');
});
});

describe('for another new valid user', () => {
beforeAll(() => {
browser.get('/');
element(by.id('add-contact')).click();
element(by.id('contact-name')).sendKeys('Grace');
});

it('should send an email address', () => {
let email = element(by.id('contact-email'));
email.sendKeys('grace@hopper.com');
expect(email.getAttribute('value')).toEqual('grace@hopper.com');
});

it('should send a phone number', () => {
let tel = element(by.css('input[type="tel"]'));
tel.sendKeys('1234567890');
expect(tel.getAttribute('value')).toEqual('1234567890');
});
});

describe('for an invalid email', () => {
beforeEach(() => {
browser.get('/add');
element(by.id('contact-name')).sendKeys('Bad Email');
});

it('should send an invalid email', () => {
let email = element(by.id('contact-email'));
email.sendKeys('baduser.com');
element(by.buttonText('Create')).click();

let invalidEmailModal = element(by.tagName('app-invalid-email-modal'));
expect(invalidEmailModal.isPresent()).toBe(true);

let modalButton = invalidEmailModal.element(by.tagName('button'));
modalButton.click();

browser.wait(EC.not(EC.presenceOf(invalidEmailModal)), 5000);
expect(invalidEmailModal.isPresent()).toBe(false);
expect(browser.getCurrentUrl()).toEqual(browser.baseUrl + '/add');
});

it('should also send an invalid email', () => {
let email = element(by.id('contact-email'));
email.sendKeys('@baduser.com');
let invalidEmailModal = element(by.tagName('app-invalid-email-modal'));
expect(invalidEmailModal.isPresent()).toBe(false);
});
});

describe('for an invalid phone number', () => {
beforeEach(() => {
browser.get('/add');
element(by.id('contact-name')).sendKeys('Bad Tel');
});

it('should send an invalid tel', () => {
let tel = element(by.css('input[type="tel"]'));
tel.sendKeys('123-456-7890');
element(by.buttonText('Create')).click();
let invalidTelModal = element(by.tagName('app-invalid-phone-number-modal'));
expect(invalidTelModal.isDisplayed()).toBe(true);
let modalButton = invalidTelModal.element(by.tagName('button'));
modalButton.click();

browser.wait(EC.not(EC.presenceOf(invalidTelModal)), 5000);
expect(invalidTelModal.isPresent()).toBe(false);
expect(browser.getCurrentUrl()).toEqual(browser.baseUrl + '/add');
});

it('should also send an invalid tel', () => {
let tel = element(by.css('input[type="tel"]'));
tel.sendKeys('12345678901');
element(by.buttonText('Create')).click();
let invalidTelModal = element(by.tagName('app-invalid-phone-number-modal'));
expect(invalidTelModal.isDisplayed()).toBe(true);
let modalButton = invalidTelModal.element(by.tagName('button'));
modalButton.click();

browser.wait(EC.not(EC.presenceOf(invalidTelModal)), 5000);
expect(invalidTelModal.isPresent()).toBe(false);
expect(browser.getCurrentUrl()).toEqual(browser.baseUrl + '/add');
});
});
});
26 changes: 26 additions & 0 deletions e2e/po/base.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { browser as globalBrowser, element as globalElement, ElementHelper,
ExpectedConditions as globalExpectedConditions, ProtractorBrowser,
ProtractorExpectedConditions } from 'protractor';
import { promise as wdpromise } from 'selenium-webdriver';

export class PageObject {
browser: ProtractorBrowser;
element: ElementHelper;
expectedConditions: ProtractorExpectedConditions;

constructor(browser?: ProtractorBrowser) {
if (browser) {
this.browser = browser;
this.element = browser.element;
this.expectedConditions = browser.ExpectedConditions;
} else {
this.browser = globalBrowser;
this.element = globalElement;
this.expectedConditions = globalExpectedConditions;
}
}

getCurrentUrl() {
return this.browser.getCurrentUrl();
}
}
Loading

0 comments on commit 6367383

Please sign in to comment.