Skip to content

Commit

Permalink
migration - chapter08 (testing-angular-applications#1)
Browse files Browse the repository at this point in the history
Previous commit messages from chapter-8-code:

* test(simple): write first test to find the contact list title (testing-angular-applications#1)

- Set up repo similar to the angular-cli e2e directory structure.
  Protractor.conf.js, tsconf.json, and tslint.json match the
  angular-cli files.
- Update dependencies and devDependencies to run Protractor test.
- Add scripts to package.json to transpile typescript files,
  download chromedriver binary, and run the protractor test.
- Use a page object called contact-list.po.
- Write a test using the page object. the test verifies that the
  title is not null and matches the title.

* test(simple): create a protractor config for the simple test (testing-angular-applications#2)

- use the protractor.conf.js as the base configuration
- edit the specs array to run only the first test

* test(first-test): create a barebones test (testing-angular-applications#3)

The first test is runnable with `npm run e2e test-8-0.conf.js`.

Running the first test command should launch the `pree2e` command.
This will launch webdriver-manager to download the chromedriver
binary.

Protractor will then run the `e2e/test-8-0.e2e-spec.ts` file and
navigate to https://angularjs.org and check to see if the current url
matches.

* test(8.1): example for section 8.1 (testing-angular-applications#4)

* test(8.2): example for section 8.2 (testing-angular-applications#5)

Test adding a new contact by:
- clicking the add button
- filling out the contact form
- clicking the submit button

Covers:
- finding elements with  by.id and by.buttonText
- interacting with elements by clicking and sending keys
- browser alerts and dismissing them
- waiting for expected conditions

* try(circleci): add circleci 2.0 (testing-angular-applications#9)

Use circleci 2.0 with directConnect. This does not take advantage of
docker-compose but this is a good starting point.

* test(8.1 and 8.2): update for jenkins (testing-angular-applications#12)

* test(element): a collection of element methods (testing-angular-applications#13)

* test(tables): examples for `by` and `element` methods (testing-angular-applications#14)

* test(element.all): examples to filter, map, and reduce (testing-angular-applications#15)

* test(pageobjects): add page objects to test-8-4 (testing-angular-applications#16)

* fix package.json details (testing-angular-applications#17)

* update readme to include circle ci build shield (testing-angular-applications#18)

* improve tables to have more examples and comments (testing-angular-applications#19)

* change the alert message dismiss to modal click (testing-angular-applications#20)

* after dismissing the modal, use expected conditions to check that it is not there (testing-angular-applications#21)

* fix tests for ElementArrayFinder and TypeScript errors (testing-angular-applications#22)

* rename files to more meaningful names. (testing-angular-applications#23)

- rename *.e2e-spec.ts files to more meaningful names
- rename protractor configuration files to more meaningful names
- add clang formatting and format everything
- update circleci to run all test suites and enforce formatting

* add linter checks (testing-angular-applications#25)

* separate out add contact into two files and use globbing in conf (testing-angular-applications#26)

* remove extra describes from the add contact tests (testing-angular-applications#27)

* clean up page objects (testing-angular-applications#28)

* use onPrepare method over beforeLaunch (testing-angular-applications#29)

* migration - move chapter 8 code to chapter08/
  • Loading branch information
cnishina authored Oct 22, 2017
1 parent 39931b0 commit 78b5ae6
Show file tree
Hide file tree
Showing 26 changed files with 1,021 additions and 0 deletions.
29 changes: 29 additions & 0 deletions chapter08/.circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
version: 2
jobs:
build:
working_directory: ~/workspace
docker:
- image: circleci/node:7-browsers
steps:
- checkout

- run: node --version
- run: npm --version
- run: java -version
- run: google-chrome --version
- run: yarn --version

- run: yarn install
- run: yarn run format-enforce
- run: yarn run lint

# Individual tests
- run: xvfb-run -a yarn run e2e protractor-first-test.conf.js
- run: xvfb-run -a yarn run e2e protractor-add-contact.conf.js
- run: xvfb-run -a yarn run e2e protractor-invalid-contact.conf.js
- run: xvfb-run -a yarn run e2e protractor-contact-list.conf.js
- run: xvfb-run -a yarn run e2e protractor-page-objects.conf.js
- run: xvfb-run -a yarn run e2e protractor-tables.conf.js

# Run all tests
- run: xvfb-run -a yarn run e2e protractor.conf.js
3 changes: 3 additions & 0 deletions chapter08/.clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Language: JavaScript
BasedOnStyle: Google
ColumnLimit: 80
32 changes: 32 additions & 0 deletions chapter08/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/dist
/tmp

# dependencies
/node_modules
/bower_components

# IDEs and editors
/.idea

# misc
/.vscode
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
/typings

# e2e
/e2e/*.js
/e2e/*.map

# unit tests
*.spec.ts

#System Files
.DS_Store
1 change: 1 addition & 0 deletions chapter08/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Chapter 8 [![CircleCI Status](https://circleci.com/gh/testing-angular-applications/chapter-8-code.svg?style=shield)](https://circleci.com/gh/testing-angular-applications/chapter-8-code)
22 changes: 22 additions & 0 deletions chapter08/e2e/add-contact.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {browser, by, element, ExpectedConditions as EC} from 'protractor';

describe('adding a new contact with only a name', () => {
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 + '/');
});
});
21 changes: 21 additions & 0 deletions chapter08/e2e/add-second-contact.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {browser, by, element, ExpectedConditions as EC} from 'protractor';

describe('adding a new contact with name, email, and phone number', () => {
beforeAll(() => {
browser.get('/');
element(by.id('add-contact')).click();
element(by.id('contact-name')).sendKeys('Grace\'s Directive');
});

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

it('should send a phone number', () => {
let tel = element(by.css('input[type="tel"]'));
tel.sendKeys('1234567890');
expect(tel.getAttribute('value')).toEqual('1234567890');
});
});
115 changes: 115 additions & 0 deletions chapter08/e2e/contact-list.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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');
});
});
9 changes: 9 additions & 0 deletions chapter08/e2e/first-test.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {browser} from 'protractor';

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

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

it('should not create a new contact with baduser.com', () => {
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 not create a new contact with @baduser.com', () => {
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('with an invalid phone number', () => {
beforeEach(() => {
browser.get('/add');
element(by.id('contact-name')).sendKeys('Bad Tel');
});

it('should not create a new contact with a formatted telephone number',
() => {
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 not create a new contact with too many numbers in the telephone number',
() => {
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');
});
});
});
70 changes: 70 additions & 0 deletions chapter08/e2e/page-objects.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {browser, by} from 'protractor';

import {COL, Contact, ContactListPageObject, NewContactPageObject} from './po';

describe('contact list', () => {
let contactList: ContactListPageObject;
let newContact: NewContactPageObject;

beforeAll(() => {
// We could also add the browser object here. For example: new
// ContactListPO(browser). This could become important if we fork the
// browser since the browser object refers to the current selenium session.
contactList = new ContactListPageObject();
});

describe('add a new contact', () => {
beforeAll(() => {
contactList.navigateTo();
});

it('should click the + button', () => {
newContact = contactList.clickPlusButton();
expect(newContact.getCurrentUrl()).toBe(browser.baseUrl + '/add');
});

it('should fill out form for a new contact', () => {
newContact.setContactInfo('Mr. Newton', 'mr.newton@example.com', null);
expect(newContact.getName()).toBe('Mr. Newton');
expect(newContact.getEmail()).toBe('mr.newton@example.com');
expect(newContact.getPhone()).toBe('');
});

it('should click the create button', () => {
contactList = newContact.clickCreateButton();
expect(contactList.getCurrentUrl()).toBe(browser.baseUrl + '/');
});
});

describe('read contact list', () => {
it('should find the new contact', () => {
let tableRow = contactList.findContact('Mr. Newton').get(0);
let tableData = tableRow.all(by.tagName('td'));
expect(tableData.get(COL.name).getText()).toBe('Mr. Newton');
expect(tableData.get(COL.email).getText()).toBe('mr.newton@example.com');
expect(tableData.get(COL.phoneNumber).getText()).toBe('');
});

it('should find the contacts and verify each value', () => {
let contacts = contactList.getContacts();
contacts.then((contacts: Contact[]) => {
expect(contacts.length).toBe(5);
expect(contacts[0]).toBeDefined();
expect(contacts[1].email).toBe('rusty.component@example.com');
expect(contacts[2].tel).toBe('+1 (714) 555-0111');
expect(contacts[3].name).toBe('Craig Service');
expect(contacts[4].name).toBe('Mr. Newton');
});
});

it('should get the contact names so we can print them to console', () => {
let contactNames = contactList.getContactNames();
let expectedNames =
'Adrian Directive, Rusty Component, Jeff Pipe, Craig Service, Mr. Newton';
expect(contactNames).toBe(expectedNames);
contactNames.then(text => {
console.log(text);
});
});
});
});
Loading

0 comments on commit 78b5ae6

Please sign in to comment.