Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/tonybranfort/ngpo

Simple template to create page objects for AngularJs Protractor tests
https://github.com/tonybranfort/ngpo

angularjs pageobject pageobject-pattern protractor protractor-tests

Last synced: 13 days ago
JSON representation

Simple template to create page objects for AngularJs Protractor tests

Awesome Lists containing this project

README

        

# ngpo
Create page objects with helper functions for [AngularJs Protractor](http://www.protractortest.org/#/) tests using a simple object template.

### I want:
* Protractor tests as quick to write, easy to read and ignorant of the page elements as possible.
* Simple and consistent protractor page objects which can be nested.
* Consistent Protractor test methods across applicable elements and html widgets including:
- [enterValue](#enterValue) `clientPo.name.enterValue('franky');`
- [getValue](#getValue) `expect(clientPo.name.getValue()).toBe('franky');`
- [isVisible](#isVisible) (true if both isPresent and isDisplayed) `expect(clientPo.name.isVisible()).toBe(true);`
- No impact to existing protractor methods (unless explicitly intended)
* Simple and consistent way of accessing repeating and nested page elements `expect(clientPo.payments.getRow(1).amount.getValue()).toBe('423');`
* Ability to easily attach custom functions to page objects.
* Ability to easily incorporate other html widgets into ngpo.
* Page Object code that's not repeated.

Install with `npm install ngpo`.

**ngpo v2.x**: requires nodejs 6.x or greater (no breaking api changes from ngpo v1.x). Tested with angularJS v1.5.0 & 1.6.5, Protractor v5.1.2, Chrome v60.0, and chromedriver 2.31.

**ngpo v1.x**: Tested with Protractor versions 2.5 and 5.1.

## Examples

See [test\test.js](https://github.com/tonybranfort/ngpo/blob/master/test/test.js) and [test\client.po.js](https://github.com/tonybranfort/ngpo/blob/master/test/client.po.js) for a full set of examples.

#### Create a page object of Input, Dropdown & Button elements using [jsfiddle ngpo example](https://jsfiddle.net/tbmpls/vm6sL9zv/).
```javascript
var ngpo = require('ngpo');

var els = {
nameInput: {
locator: by.model('client.name'),
po: ngpo.makeInputPo},
name: {
locator: by.binding('client.name'),
po: ngpo.makeTextPo},
clearNameButton: {
locator: by.id('clear-name-button'),
po: ngpo.makeButtonPo},
typeDd: {
locator: by.model('client.type'),
po: ngpo.makeDdSelectPo}
};

var pos = ngpo.makePos(els);

```
And run a protractor test with those elements.
```javascript
var clientPo = require('client.po.js');

describe('client', function() {

it('should allow name to be modified', function() {
clientPo.nameInput.enterValue('franky');
expect(clientPo.nameInput.getValue()).toBe('franky');
expect(clientPo.name.getValue()).toBe('franky');

it('should clear the name', function() {
clientPo.clearNameButton.click();
expect(clientPo.name.getValue()).toBe('');
expect(clientPo.nameInput.getValue()).toBe('');
});

it('should allow client type to be selected', function() {
clientPo.typeDd.enterValue('cranky');
expect(clientPo.typeDd.getValue()).toBe('cranky');
expect(clientPo.type.getValue()).toBe('cranky');
});

});

```

#### Create a list page object using [jsfiddle ngpo example](https://jsfiddle.net/tbmpls/vm6sL9zv/).
```javascript
var ngpo = require('ngpo');

//Nest the list elements as another 'els' object in the list object ('payments' here):
var els = {
addPaymentButton: {
locator: by.id('add-payment-button'),
po: ngpo.makeButtonPo},
payments: {
locator: by.repeater('payment in payments'),
po: ngpo.makeListPo,
els: {
amountInput: {
locator: by.model('payment.amount'),
po: ngpo.makeInputPo},
amount: {
locator: by.binding('payment.amount'),
po: ngpo.makeTextPo},
},
}
};

var pos = ngpo.makePos(els);

```

Run the protractor test.
```javascript
var clientPo = require('client.po.js');

describe('client', function() {

it('should have working payment inputs', function() {
expect(clientPo.payments.count()).toBe(0);

clientPo.addPaymentButton.click()
.then(() => {
expect(clientPo.payments.count()).toBe(1);
// getRow() is the ngpo method to retrieve the row's nested elements.
// ngpo uses getRow() so as to not overwrite the get() method.
return clientPo.payments.getRow(0).amountInput.enterValue(5);
})
.then(() => {
expect(clientPo.payments.getRow(0).amountInput.getValue()).toBe('5');
expect(clientPo.payments.getRow(0).amount.getValue()).toBe('5');

return clientPo.addPaymentButton.click();
})
.then(() => {
expect(clientPo.payments.count()).toBe(2);
expect(clientPo.payments.getRow(0).amount.getValue()).toBe('5');
return clientPo.payments.getRow(1).amountInput.enterValue('423');
})
.then(() => {
expect(clientPo.payments.getRow(1).amountInput.getValue()).toBe('423');
expect(clientPo.payments.getRow(1).amount.getValue()).toBe('423');
});

});

});

```

## Documentation

### ngpo Functions available:
* [`makePos`](#makePos)
* Make page object functions and the functions attached (in addition to all expected Protractoor functions):
* [`makeDefaultPo`](#makeDefaultPo)
- [isVisible](#isVisible)
* [`makeTextPo`](#makeTextPo)
- [getValue](#getValue)
- [getValueTrim](#getValueTrim)
- [isVisible](#isVisible)
* [`makeInputPo`](#makeInputPo)
- [enterValue](#enterValue)
- [getValue](#getValue)
- [getValueTrim](#getValueTrim)
- [isVisible](#isVisible)
* [`makeDateInputPo`](#makeDateInputPo)
- [enterValue](#enterValue)
- [getValue](#getValue)
- [getValueTrim](#getValueTrim)
- [isVisible](#isVisible)
- getValueMmddyyyy
- getValueYyyymmdd
* [`makeButtonPo`](#makeButtonPo)
- [isVisible](#isVisible)
* [`makeButtonWithPausePo`](#makeButtonWithPausePo) (**deprecated**)
* [`makeDdSelectPo`](#makeDdSelectPo)
- [enterValue](#enterValue)
- [getValue](#getValue)
- [getValueTrim](#getValueTrim)
- [isVisible](#isVisible)
- clear (selects first item in dd list)
* [`makeParentPo`](#makeParentPo)
- [getValue](#getValue)
- subPo.poFn()
* [`makeListPo`](#makeListPo)
- getRow
- getRow(n).subPo.poFn()
- getCount
- getValue
* poFns (optional fns that can be attached to page objects; see [Append custom functions to page object elements](#custom-fns))
- `hasClass`, `makeHasClassFn` : `expect(clientPo.deleteHobbyButton.hasClass('yada')).toBe(true)`
- `clearByBs`, `makeHasClearByBs` (use to clear date field in Chrome; Protractor issue #562) : `clientPo.dobInput.clearByBs()`
- `clickWithPause` (**deprecated**)
- `getAttributeValue`
* Other functions
* pause
* acceptAlert
* dismissAlert

#### How to
* [Append custom functions to page object elements](#custom-fns)
* Clear a date field in Chrome (Protractor Issue [#562](https://github.com/angular/protractor/issues/562)) - Use `poFns.clearByBs`. See example in [Append custom functions to page object elements](#custom-fns).
* [Nest ngpo page objects](#nesting-page-objects).
* Create your own _makePo_ functions: See [`makeDefaultPo`](#makeDefaultPo). And github/npm it: See [ngpo-ui-select](https://www.npmjs.com/package/ngpo-ui-select).

### ngpo functions attached to page objects
#### enterValue()
Enters a value into a page element and returns a Promise. How value is entered depends on the page element. Input elements use:
```javascript
return el
.click()
.clear()
.sendKeys(value)
.sendKeys(protractor.Key.TAB);

```
Example:
``` javascript
clientPo.nameInput.enterValue('franky');
expect(clientPo.nameInput.getValue()).toBe('franky');
```

#### getValue()
Returns the value of the pageObject as a Promise. How value is retrieved depends on the page element:
* makeTextPo() uses Protractor getText()
* input POs (makeInputPo, makeDateInputPo) use Protractor `getAttribute('value')`
* makeDdSelectPo uses el.$('option:checked').getText()
``` javascript
clientPo.nameInput.enterValue('franky');
expect(clientPo.nameInput.getValue()).toBe('franky'); // input
expect(clientPo.name.getValue()).toBe('franky'); // text by.binding
```

#### getValueTrim()
Returns pageObject.getValue() to a string.trim() as a Promise.
```javascript
clientPo.nameInput.enterValue(' b o bb y ');
expect(clientPo.nameInput.getValueTrim()).toBe('b o bb y');

```

#### isVisible()
Returns true as a Promise if isPresent and isDisplayed.
```javascript
expect(clientPo.showme.isPresent()).toBe(true);
expect(clientPo.showme.isDisplayed()).toBe(true);
expect(clientPo.showme.isVisible()).toBe(true);
// hide showme element
clientPo.showmeButton.click()
.then(function() {
expect(clientPo.showme.isPresent()).toBe(true);
expect(clientPo.showme.isDisplayed()).toBe(false);
expect(clientPo.showme.isVisible()).toBe(false);
});
```

### ngpo api functions
#### makePos(els)
Returns a Page Object: An object-literal of Protractor ElementFinder objects possibly with methods appended. Methods appended are based on the els object passed in.

`makePos` calls the function assigned to the `po` property for every object in the `els` object. The `po` function is called with (1) the `locator` and (2) the respective els object.

`els` is an object of the form :
```javascript
var els {
poName1: {
locator: protractorLocator // Req'd. eg; by.model('client.city')
po: makePoFn //Req'd. the function to append helper functions and return a protractor ElementFinder
els: {...} // optional nested object of same form els for list or parent pos
fns: {fnName: function} // optional object of custom functions that will be appended to this ElementFinder
yourParam: value // optional; any other parameter may be included for use in custom fns
},
poName2: ...
}
```

Example
```javascript
var ngpo = require('ngpo');

var els = {
nameInput: {
locator: by.model('client.name'),
po: ngpo.makeInputPo,
myOption: 'abc'},
};

var pos = ngpo.makePos(els);

```

In the above example, when `makePos` is called, `makeInputPo` will be called with
```javascript
makeInputPo(
by.model('clientName'),
{locator: by.model('client.name'),
po: ngpo.makeInputPo
myOption: 'abc'}
)
```

And return an object with the property `nameInput` which would be a protractor ElementFinder with getValue and enterValue methods appended (from makeInputPo).

#### makeDefaultPo(elOrLoc, options)
Returns a Protractor ElementFinder with only the `isVisible` ngpo function appended.

Determines if `elOrLoc` is a Protractor ElementFinder or locator. If it is a locator, creates an ElementFinder from it and returns that ElementFinder. Otherwise, returns ElementFinder as-is. `options` is the els object for the page object being created; eg, `{locator: by.binding('client.name'), po: ngpo.makeTextPo}`.

Every makeXxxPo function calls this function first. Custom makeXxxPo functions should also call this function first as well which will ensure that it will work as a 'sub' PO in list and parent elements.

Example
```javascript
function myCustomPo(elOrLoc, options) {
var el = makeDefaultPo(elOrLoc);

var yank = options && options.yank ? options.yank : '';

el.isBlada() = function() {
return el.getAttribute('blada') === yank;
}

return el;
}

var els = {
berl: {
locator: by.model('something.what'),
po: myCustomPo,
yank: 'green'
}
}

```

#### makeTextPo(elOrLoc, options)
Returns a Protractor ElementFinder with one appended function:
* getValue - returns element.getText()

Arguments: See [`makeDefaultPo`](#makeDefaultPo) and [`makePos`](#makePos).

Would typically be used with by.binding.

```javascript
var ngpo = require('ngpo');

var els = {
name: {
locator: by.binding('client.name'),
po: ngpo.makeTextPo}
};

var pos = ngpo.makePos(els);

```

#### makeInputPo(elOrLoc, options)
Returns a Protractor ElementFinder for html `````` with these appended functions:
* getValue - returns element.getText()
* enterValue(value) : returns ```element.click().clear().sendKeys(value).sendKeys(protractor.Key.TAB)```

Arguments: See [`makeDefaultPo`](#makeDefaultPo) and [`makePos`](#makePos).

Example
```javascript
var ngpo = require('ngpo');

var els = {
nameInput: {
locator: by.model('client.name'),
po: ngpo.makeInputPo}
};

var clientPo = ngpo.makePos(els);

// test example
clientPo.nameInput.enterValue('franky');
expect(clientPo.nameInput.getValue()).toBe('franky');

```

#### makeDateInputPo(elOrLoc, options)
Returns a Protractor ElementFinder for html `````` tags with these appended functions:
* getValue - See [`makeInputPo`](#makeInputPo)
* enterValue(value) - See `makeInputPo`
* getValueMmddyyyy - getValue() as string mm/dd/yyyy format
* getValueYyyymmdd - getValue() as string yyyy-mm-dd format

Arguments: See [`makeDefaultPo`](#makeDefaultPo) and [`makePos`](#makePos).

Example
```javascript
var ngpo = require('ngpo');

var els = {
dobInput: {
locator: by.model('client.dob'),
po: ngpo.makeDateInputPo}
};

var clientPo = ngpo.makePos(els);

// test example
clientPo.dobInput.enterValue('01/01/1939');
expect(clientPo.dobInput.getValue()).toBe('1939-01-01');
expect(clientPo.dobInput.getValueMmddyyyy()).toBe('01/01/1939');

```

#### makeButtonPo(elOrLoc, options)
Returns a Protractor ElementFinder with no appended functions.

Arguments: See [`makeDefaultPo`](#makeDefaultPo) and [`makePos`](#makePos).

Example
```javascript
var ngpo = require('ngpo');

var els = {
deleteCityButton: {
locator: by.id('delete-city-button'),
po: ngpo.makeButtonPo}
};

var clientPo = ngpo.makePos(els);

// test example
clientPo.deleteCityButton.click();

```

#### makeButtonWithPausePo(elOrLoc, options)
Returns a Protractor ElementFinder with this amended function.
- `click()` : sleeps for `options.pause` milleseconds after click()
```element.click().then(function() {pause(options.pause);});```

Arguments: See [`makeDefaultPo`](#makeDefaultPo) and [`makePos`](#makePos).

Original ElementFinder click() method is available via element.p.click().

Example
```javascript
var ngpo = require('ngpo');

var els = {
deleteHobbyButton: {
locator: by.id('delete-hobby-button'),
po: ngpo.makeButtonWithPausePo,
pause: 5000}
};

var clientPo = ngpo.makePos(els);

// test example
clientPo.deleteHobbyButton.click(); // pauses 5 seconds after click()

```

#### makeDdSelectPo(elOrLoc, options)
Returns a Protractor ElementFinder for html `````` tags
with these appended functions :
* getValue - returns ```element.$('option:checked').getText()```
* enterValue(value) : returns ```element.click().sendKeys(value).sendKeys(protractor.Key.TAB)```

Arguments: See [`makeDefaultPo`](#makeDefaultPo) and [`makePos`](#makePos).

Example
```javascript
var ngpo = require('ngpo');

var els = {
nameInput: {
locator: by.model('client.name'),
po: ngpo.makeInputPo}
};

var clientPo = ngpo.makePos(els);

// test example
clientPo.nameInput.enterValue('franky');
expect(clientPo.nameInput.getValue()).toBe('franky');

```

#### makeParentPo(elOrLoc, options)
Returns a Protractor ElementFinder which can have sub-ElementFinders on it.

Arguments: See [`makeDefaultPo`](#makeDefaultPo) and [`makePos`](#makePos).

Example
```javascript
var ngpo = require('ngpo');

var els = {
request: {
locator: by.id('request'),
po: ngpo.makeParentPo,
els: {
rInput: {
locator: by.model('client.request'),
po: ngpo.makeInputPo},
rText: {
locator: by.binding('client.request'),
po: ngpo.makeTextPo},
}
},
};

var clientPo = ngpo.makePos(els);

// test example
clientPo.request.rInput.enterValue('be nice');
expect(clientPo.request.rInput.getValue()).toBe('be nice');
expect(clientPo.request.rText.getValue()).toBe('be nice');

```

#### makeListPo(elOrLoc, options)
Returns a Protractor element.all object with nested protractor ElementFinders. It has these appended functions:
* getRow(n)
* getRow(n).subPo.poFn()
* getCount
* getValue

Arguments: See [`makeDefaultPo`](#makeDefaultPo) and [`makePos`](#makePos).

Example
```javascript
var ngpo = require('ngpo');

var els = {
payments: {
locator: by.repeater('payment in payments'),
po: ngpo.makeListPo,
els: {
amountInput: {
locator: by.model('payment.amount'),
po: ngpo.makeInputPo},
amount: {
locator: by.binding('payment.amount'),
po: ngpo.makeTextPo},
}
};

var clientPo = ngpo.makePos(els);

// test example
expect(clientPo.payments.getCount()).toBe(1);
clientPo.payments.getRow(0).amountInput.enterValue(5);
expect(clientPo.payments.getRow(0).amountInput.getValue()).toBe('5');
expect(clientPo.payments.getRow(0).amount.getValue()).toBe('5');

```

#### How to Nest Page Objects

Example to nest the transportation page object into a client page object.

```javascript
// transportation.po.js
var ngpo = require('../lib/index.js');

var els = {
transportationInput: {
locator: by.model('client.transportation'),
po: ngpo.makeInputPo},
transportation: {
locator: by.binding('client.transportation'),
po: ngpo.makeTextPo},

};

var pos = ngpo.makePos(els);

module.exports = pos;

```

There are 2 ways to nest the above transportation page object.
```javascript
// client.po.js
var ngpo = require('ngpo');
var transPo = require('./transportation.po.js');

// (1) directly in the client po els object
var els = {
transportationParent: {
locator: by.id('trans-parent'),
po: ngpo.makeParentPo,
els: transPo.els},
};

// (2) append the transportation page objects elements directly to the client po object
var pos = ngpo.makePos(els);
pos = ngpo.makePos(transPo.els, pos);

```

The protractor tests would refer to these like this:
```javascript
// #1 above would be called using transportationParent:
clientPo.transportationParent.transportationInput.enterValue('strides');
expect(clientPo.transportationParent.transportationInput.getValue()).toBe('strides');

// #2 would allow the transportation page object elements to be called directly from the client po
clientPo.transportationInput.enterValue('unicycle');
expect(clientPo.transportationInput.getValue()).toBe('unicycle');

```

#### How to append custom functions to page object elements
Custom functions can be included in the els `fns` property object. Custom functions are called with the ElementFinder and the options object (see [makeDefaultPo](#makeDefaultPo)), so that you can refer to them in the custom function, and the arguments that you call the function with from the protractor script.

Example
```javascript

var els = {
hobbyInput: {
locator: by.model('client.hobby'),
po: ngpo.makeInputPo,
pause: 5000,
fns: {
getClasses: function(el, options) {return el.getAttribute('class');},
clickWithPause: function(el, options) {
return el.click().then(function() {browser.sleep(options.pause);})
}}
},
funnyInput: {
locator: by.model('client.funny'),
po: ngpo.makeInputPo,
fns: {
inputAddedVals: (funnyInputEl, options, val1, val2) => {
var addedVals = val1 + val2;
return funnyInputEl.enterValue(addedVals);
}
}
},
dobInput: {
locator: by.model('client.dob'),
po: ngpo.makeDateInputPo,
// override clear() fn with poFns.clearByBs b/c Protractor Issue #562
fns: {clear: ngpo.poFns.clearByBs}},
};

// test example
expect(clientPo.hobbyInput.getClasses()).toContain('yada');
clientPo.hobbyInput.clickWithPause(); // pauses 5 seconds after click()

clientPo.funnyInput.inputAddedVals(3,4)
.then(function(){
expect(clientPo.funnyInput.getValue()).toBe('7');
});

clientPo.dobInput.enterValue('01/02/1987')
.then(() => {
expect(clientPo.dobInput.getValue()).toBe('1987-01-02');
clientPo.dobInput.clear(); // using poFns.clearByBs
expect(clientPo.dobInput.getValue()).toBe('');
});

```