NemoJS'and'Applitools'Eyes
Visual'Tes*ng'with'node.js
Where%does%Nemo%
fit?
A"basic"JavaScript"selenium3webdriver"script:
var webdriver = require('selenium-webdriver'),
SeleniumServer = require('selenium-webdriver/remote').SeleniumServer;
var server = new SeleniumServer(pathToSeleniumJar, {
port: 4444
});
server.start();
var driver = new webdriver.Builder().
usingServer(server.address()).
withCapabilities(webdriver.Capabilities.firefox()).
build();
driver.get('https://www.paypal.com');
The$same$script$using$Nemo
var Nemo = require('nemo');
var nemo = Nemo(function () {
nemo.driver.get('https://www.paypal.com');
});
What%does%Nemo%do?
• provides*environment.aware*JSON*configura9on
• provides*plugin'API
• provides*access*to*the*webdriver*API
The$webdriver$API
exposed'as'nemo.driver'and'nemo.wd
Environment*aware*configura1on
• config.json"is"the"defaults"file
• add"overrides"based"on"NODE_ENV
• e.g."NODE_ENV=local"looks"for"local.json
Configura)on*protocols
• shortstop(handlers
• env:foo
• path:foo
• argv:foo
• config:foo.bar
config.json
{
"plugins": {
"view": {
"module": "nemo-view"
},
"util": {
"module": "path:plugin/util"
}
},
"driver": {
"browser": "chrome"
}
}
Nemo%code%pa*erns
Using&nemo*view
it('should execute high level functionality using flow modules', function (done) {
//login
nemo.driver.get(nemo.data.baseUrl);
util.waitForJSReady(nemo);
nemo.view.login.emailWaitVisible().sendKeys('me@mine.com');
nemo.view.login.password().sendKeys('11111111');
nemo.view.login.button().click();
//add card success
nemo.view.card.numberWaitVisible().sendKeys('123456789012');
nemo.view.card.typeOptionText('Misa');
nemo.view.card.button().click();
nemo.view.card.successWait();
//add card fail
nemo.view.card.number().clear();
nemo.view.card.number().sendKeys('1001001');
nemo.view.card.typeOptionText('Misa');
nemo.view.card.button().click();
nemo.view.card.failureWait();
...
DRY$pa'ern$(card$module)
var Card = function (nemo) {
this.nemo = nemo;
};
var _enterForm = function (nemo, number, type) {
nemo.view.card.numberWaitVisible().clear();
nemo.view.card.number().sendKeys(number);
nemo.view.card.typeOptionText(type);
return nemo.view.card.button().click();
}
Card.prototype.addSuccess = function(number, type) {
_enterForm(this.nemo, number, type);
return this.nemo.view.card.successWait();
};
Card.prototype.addFailure = function(number, type) {
_enterForm(this.nemo, number, type);
return this.nemo.view.card.failureWait();
};
module.exports = Card;
DRY$pa'ern$(navigate$module)
var util = require('../util');
var Navigate = function (nemo) {
this.nemo = nemo;
};
...
Navigate.prototype.logout = function() {
this.nemo.view.nav.logoutLink().click();
return this.nemo.view.login.emailWaitVisible();
};
Navigate.prototype.bank = function() {
this.nemo.view.nav.bankLink().click();
return this.nemo.view.bank.numberWaitVisible();
};
Navigate.prototype.card = function() {
this.nemo.view.nav.cardLink().click();
return nemo.view.card.numberWaitVisible();
};
module.exports = Navigate;
DRY$pa'ern$(spec$usage)
it('should execute high level functionality using flow modules', function (done) {
navigate.loginFailure('fail@fail.com', '11111111');
navigate.loginSuccess('me@mine.com', '11111111');
card.addSuccess('0123456789012345', 'Misa');
card.addFailure('1001001', 'Misa');
bank.addSuccess('0432787332', '92929');
bank.addFailure('1001001', '92929');
navigate.logout().then(util.doneSuccess(done), util.doneError(done));
});
countries.js,(dynamic,data)
module.exports = [
{
"locality": "en-US",
"url": "http://localhost:8000/responsive/us"
},
{
"locality": "de-DE",
"url": "http://localhost:8000/responsive/de"
}
];
eyes$spec.js
dd(countries, function () {
it('should let me reply to an email for locale {locality}', function (country, done) {
//login
nemo.driver.get(country.url);
nemo.waitForDom();
nemo.view._find('#reply').click();
nemo.view._find('#forward').click();
nemo.view._find('#moveto').click();
nemo.driver.sleep(2000);
nemo.view._find('#verify').getText().
then(function (verifyText) {
nemo.assert.equal(verifyText, 'replyforwardmoveto');
}).
then(function () {
done();
}).thenCatch(function (err) {
done(err);
});
});
});
Demo:&Running&our&localized&test
Visual'bug'introduced'DE'transla3on
Incorporate*Applitools
Catch&the&visual&bug&in&the&future
Basic&configura-on
"eyes": {
"module": "path:plugin/eyes",
"arguments": [
{
"sdk": {
"setApiKey": "env:applitools_api_key",
"setMatchLevel": "Layout"
},
"viewport": {
"width": 1200,
"height": 600
},
"mock": "env:applitools_mock"
}
]
}
Demo:&Run&one&test&to&applitools
Add#responsive#tes-ng
new$config$for$each$form$factor
phone.json
{
"plugins": {
"eyes": {
"module": "path:plugin/eyes",
"arguments": [{
"sdk": {
"setBatch": "PHONE",
"setMatchLevel": "Layout"
},
"viewport": {
"width": 620,
"height": 400
}
}]
}
}
}
tablet.json
{
"plugins": {
"eyes": {
"module": "path:plugin/eyes",
"arguments": [{
"sdk": {
"setBatch": "PHONE",
"setMatchLevel": "Layout"
},
"viewport": {
"width": 950,
"height": 600
}
}]
}
}
}
Demo:&Visual&tes.ng&along&two&
dimensions
Per$country,$per$form$factor
THANK&YOU
• h#ps://applitools.com/
• h#p://nemo.js.org
• @nemojs_news
• h#ps://github.com/paypal/nemo

PayPal's NemoJS and Applitools Eyes - Visual Testing with Node.js