nemo-view
View Interface for nemo views.Installation
- Add dependencies to your
package.json
file and install
...
"nemo": "^2.0.0",
"nemo-view": "^2.0.0",
...
- Add
nemo-view
to your plugins nemo config
{
"driver": {
...
},
"plugins": {
"view": {
"module": "nemo-view",
"arguments": ["path:locator"]
}
},
"data": {
...
}
}
locatorDefinition
ThelocatorDefinition
can either be a JSON object like this:{
"locator": ".myClass",
"type": "css"
}
Where
type
is any of the selenium locator strategies. A locator
or type
CANNOT be empty/blank/absent in JSON object representation of locatorDefinition
. An error will be thrown during the setup of nemo-view. If type
under locatorDefinition
is invalid (not amongst allowed types) then an error is thrown as well.Or
locatorDefinition
can be a string like this:"css:.myClass"
String of the form <type>:<locator>
or <locator>
(where <type>
will be assumed as css)Writing a locator file
The locator JSON file describes elements and the locator strategy used for each one. The most common use case is to store all your locator files in thenemoBaseDir
+ /locator directory. The below example uses the JSON style locatorDefinition
.textBox.json
{
"fooText": {
"locator": "#foo input.texty",
"type": "css"
},
"fooButton": {
"locator": "#foo input[type='button']",
"type": "css"
},
"barText": {
"locator": "#bar input.texty",
"type": "css"
},
"barButton": {
"locator": "#bar input[type='button']",
"type": "css"
},
"bingText": {
"locator": "#bing input.texty",
"type": "css"
},
"bingButton": {
"locator": "#bing input[type='button']",
"type": "css"
},
"bangText": {
"locator": "#bang input.texty",
"type": "css"
},
"bangButton": {
"locator": "#bang input[type='button']",
"type": "css"
},
"outBox": {
"locator": "outy",
"type": "id"
}
}
nemo-view
supports for adding JavaScript-style comments in your json files as each file is processed by using shushUsing views
Note: code snippets are minus the require blocks for the sake of brevity. Please see unit tests for full filesWithout locator files
If you don't have any locator files, or don't configure nemo-view to find locator files, you will still get back convenience methods on the nemo object:nemo.view._find
nemo.view._finds
nemo.view._present
nemo.view._visible
nemo.view._wait
nemo.view._waitVisible
nemo.view._optionValue
nemo.view._optionText
nemo.view._firstVisible
Which can be used as follows:
describe('nemo-view @verySimple@', function () {
before(function (done) {
nemo = Nemo(done);
});
after(function (done) {
nemo.driver.quit().then(done);
});
beforeEach(function (done) {
nemo.driver.get(nemo.data.baseUrl);
util.waitForJSReady(nemo).then(util.doneSuccess(done), util.doneError(done));
});
it('should use the form view to enter values and write to outy div @useView@', function (done) {
nemo.view._find('css:#outy').getTagName().then(function (tn) {
assert.equal(tn.toLowerCase(), 'div');
});
nemo.view._finds('body').then(function (bodyArray) {
return bodyArray[0].getTagName();
}).then(function (tn) {
assert.equal(tn.toLowerCase(), 'body');
}).then(done, util.doneError(done));
});
});
These generic - or "underbar" - methods are defined below.
With locator files
If you've configured nemo-view properly, and have the following locator files:<nemoBaseDir>
|- locator
|- form.json
|- formElementList.json
|- select.json
|- simple.json
|- sub
|- form.json
You will get back the following views on the nemo object:
nemo.view.form
nemo.view.formElementList
nemo.view.select
nemo.view.simple
nemo.view.sub.form
Each including a set of helper methods for each locator as documented below. And usable as follows:
describe('nemo-view @simpleViewSuite@', function () {
before(function(done) {
nemo = Nemo(done);
});
after(function(done) {
nemo.driver.quit().then(done);
});
beforeEach(function (done) {
nemo.driver.get(nemo.data.baseUrl);
util.waitForJSReady(nemo).then(util.doneSuccess(done), util.doneError(done));
});
it('should use the form view to enter values and write to outy div @useView@', function (done) {
nemo.view.form.fooText().sendKeys('foo');
nemo.driver.sleep(300);
nemo.view.form.fooButton().click();
nemo.view.form.barText().sendKeys('bar');
nemo.view.form.barButton().click();
nemo.view.form.bingText().sendKeys('bing');
nemo.view.form.bingButton().click();
nemo.view.form.bangText().sendKeys('bang');
nemo.view.form.bangButton().click();
nemo.driver.sleep(3000);
nemo.view.form.outBox().getText().then(function (outText) {
assert.equal(outText, 'foobarbingbang');
done();
}, util.doneError(done));
});
});
Combining methods from with and without locator files
You can pass locators from[locatorName]By
to underscore methods like _find
or _finds
etc. For example,nemo.view._finds(nemo.view.paypal.languageBy()).then(function(languages){
languages.forEach(function(language){
//do stuff
});
});
Finding elements under another element
nemo.view._finds('div.fielder', nemo.view.simple.parentBanner()).then(function (divs) {
//do stuff
});
Creating nemo plugins with self contained views and flows
You may want to publish complete flows as a nemo plugin. That way you can import the functionality and access as a plugin. The following is an example of that.Please see the
test/contained-functionality.js
test file and test/plugin/shared-fn-plugin.js
plugin file for an example of this.View features
Generic/underbar methods
The following generic methods are added tonemo.view
find(locatorDefinition, parentWebElement)
@argument locatorDefinition {String|Object}
- Please see locatorDefinition
above@argument parentWebElement {WebElement} (optional, default driver)
- parent WebElement to search elements underneath@returns {Promise}
resolves to a WebElement or rejectedfinds(locatorDefinition, parentWebElement)
@argument locatorDefinition {String|Object}
- Please see locatorDefinition
above@argument parentWebElement {WebElement} (optional, default driver)
- parent WebElement to search elements underneath@returns {Promise}
resolves to an array of WebElements or rejectedpresent(locatorDefinition, parentWebElement)
@argument locatorDefinition {String|Object}
- Please see locatorDefinition
above@argument parentWebElement {WebElement} (optional, default driver)
- parent WebElement to search elements underneath@returns {Promise}
resolves to true or falsevisible(locatorDefinition, parentWebElement)
@argument locatorDefinition {String|Object}
- Please see locatorDefinition
above@argument parentWebElement {WebElement} (optional, default driver)
- parent WebElement to search elements underneath@returns {Promise}
resolves to true or false. Rejected if element is not foundwait(locatorDefinition, timeout , msg)
@argument locatorDefinition {String|Object}
- Please see locatorDefinition
above@argument timeout {Integer} (optional, default 5000)
- ms to wait until rejecting@argument msg {String} (optional)
- Message to accompany error in failure case`@returns {Promise}
resolves to true or rejectedwaitVisible(locatorDefinition, timeout , msg)
@argument locatorDefinition {String|Object}
- Please see locatorDefinition
above@argument timeout {Integer} (optional, default 5000)
- ms to wait until rejecting@argument msg {String} (optional)
- Message to accompany error in failure case`@returns {Promise}
resolves to true or rejectedoptionValue(locatorDefinition, value , parentWebElement)
@argument locatorDefinition {String|Object}}
- Please see locatorDefinition
above@argument value {String}
- the value attribute of the option you wish to select@argument parentWebElement {WebElement} (optional, default driver)
- parent WebElement to search elements underneathoptionText(locatorDefinition, text , parentWebElement)
@argument locatorDefinition {String|Object}}
- Please see locatorDefinition
above@argument text {String}
- The text in the option you wish to select@argument parentWebElement {WebElement} (optional, default driver)
- parent WebElement to search elements underneathfirstVisible(locatorObject, timeout)
@argument locatorObject {Object}
- Object of key/value pairs where the key describes the element to find and the
value is a locatorDefinition
(see above). Example would be:{
'loginerror': '.notification.notification-critical',
'profile': '#contents[role=main]'
}
@argument timeout {Integer} (optional, default 5000)
- ms to wait until rejecting@returns {Promise}
resolves to the found key (e.g. 'loginerror' or 'profile' from above example) or rejectedaddView method
The addView method will be added to the nemo.view namespace with the following signature:nemo.view.addView(viewSpec, addToNamespace);
viewSpec {String|JSON} will be either a string, or a JSON object to define the view/locator. addToNamespace {boolean} (optional, defaults to true) if
false
nemo-view will not attach the view to the nemo.view
namespaceUsing the addView method, you can add a view at any time using the same formats and conventions as if you are adding them in the Nemo.setup() method. Examples:
//add using a locator in the autoBaseDir/locator directory
var myPage = nemo.view.addView('myPage');
var login = nemo.view.addView({
"name": "login",
"locator": "path:locator/loggedOut/login"
});
var addCard = nemo.view.addView({
"name": "addCard",
"locator": "module:nemo-paypal-locators/addCard"
});
The addView method will return the view object. It will also dedupe to prevent extra cycles adding the same view multiple times, or overwriting of a view with another of the same name.
locator methods
The view will create the following methods for each locator object:locatorName
- arguments: none
- returns: Promise which resolves to a WebElement or to a list if a list locator is used. Rejects if an error occurs.
The list locator
This is a special locator, which let's you find lists of related elements. For example, given the locator fileformElementList.json
:{
"inputGroup": {
"locator": "div.fielder",
"type": "css",
"Elements": {
"text": "input.texty",
"button": "input[type='button']"
}
}
}
You will get a list of items, each with two properties
text
and button
which themselves return WebElements when called.nemo.view.formElementList
.inputGroup()
.then((elements) => {
elements.forEach(function (el) {
el.text().sendKeys('abcd');
el.button().click();
})
});
locatorNameBy or locatorName.by
- arguments: none
- returns: JSON locator object. You can use this, for example, to pass to selenium-webdriver until statements
locatorNamePresent or locatorName.present
- arguments: none
- returns: Promise which resolves to true or false
locatorNameWait or locatorName.wait
- arguments
- returns: Promise which resolves to WebElement when element is present, or reject
locatorNameWaitVisible or locatorName.waitVisible
- arguments
- returns: A promise which resolves to WebElement when element is both found and visible, or reject
locatorNameVisible or locatorName.visible
- arguments: none
- returns: Promise which resolves to true or false when element is present, or rejected if element is not present
locatorNameOptionText or locatorName.optionText
- arguments
- returns: Promise which resolves to true when option is selected
locatorNameOptionValue or locatorName.optionValue
- arguments
- returns: Promise which resolves to true when option is selected
locatorNameTextEquals or locatorName.textEquals
- arguments
- returns: Promise which resolves to true when the expected text matches, or rejects when it doesn't
locatorNameAttrEquals or locatorName.attrEquals
- arguments
- returns: Promise which resolves to true when the expected text matches, or rejects when it doesn't
Using locator specialization
You can specify different locator strings/strategies based on thedata.locale
configuration value.
To do so, first modify the entry you wish to be specialized:{
"myLocator": {
"type": "css",
"locator": ".myLocator"
}
}
changes to
{
"myLocator": {
"default": {
"type": "css",
"locator": ".myLocator"
},
"DE": {
"type": "css",
"locator": ".meinLocator"
}
}
}
You can set the
data.locale
property as follows:nemo._config.set('data:locale', 'DE');
For a working example, refer to the unit tests for the locale feature (found in
test/locale.js
) in this module.NOTE: This feature is a carry-over from earlier versions of nemo-view. It is understood now that the above feature does not actually represent "locale" specificity as defined by bcp47 (https://tools.ietf.org/html/bcp47). See discussion here and issue here. Follow along as we discuss a backwards compatible way to resolve this unfortunate nomenclature error.
Unit Tests
- Unit tests run by default using headless browser PhantomJS. To run unit tests out of box, You must have PhantomJS installed on your system and must be present in the path
* Download PhantomJS from [here](http://phantomjs.org/download.html)
* On OSX, you can optionally use `brew` to install PhantomJS like `brew install phantomjs`
* PhantomJS installation detailed guide on Ubuntu can be found [here](https://gist.github.com/julionc/7476620)
- If you want to run unit tests on your local browser, like lets say Firefox, you need to update browser in unit test
test/config/config.json
like here- How to run unit tests?
npm run test:unit
will just run unit tests
npm test
- default grunt task will run linting as well as unit tests
To run directly using mocha assuming its globally installed on your system mocha -t 60s
Run using mocha on a specific test, mocha --grep @_visible@withParent@negative@ -t 60s
Or post npm install
on nemo-view module, you can run node_modules/.bin/mocha --grep @_visible@withParent@negative@ -t 60s