diff --git a/CHANGELOG.md b/CHANGELOG.md index db9ce6b..342a545 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # nemo-view changelog +## v1.2.0 + +* add realtime locale switching support (see https://github.com/paypal/nemo-view/issues/31) + +## v1.1.0 + +* add new methods `[locatorName]TextEquals` and `[locatorName]AttrEquals` (see https://github.com/paypal/nemo-view/pull/29) +* add optional msg parameter to generic methods (see https://github.com/paypal/nemo-view/pull/28) + +## v1.0.3 + +Change API to match `nemo@1.0` + ## v0.3.2-beta Add support for module based locators and path based locators. I.e. you can put locators into commonjs modules as well as into other directory structure diff --git a/README.md b/README.md index 73203c8..012be9b 100644 --- a/README.md +++ b/README.md @@ -379,9 +379,46 @@ Other than that, the nemo-view uses nemo-locatex internally, so if you change yo * value: the expected value for the element * returns: Promise which resolves to true when the expected text matches -## Using LOCALE specific locators +## Using locator specialization -Please see these sections in the nemo-locatex README: -* https://github.com/paypal/nemo-locatex#changing-your-locator-files -* https://github.com/paypal/nemo-locatex#setting-locale +You can specify different locator strings/strategies based on the `data.locale` configuration value. +To do so, first modify the entry you wish to be specialized: +```js +{ + "myLocator": { + "type": "css", + "locator": ".myLocator" + } +} +``` + +changes to + +```js +{ + "myLocator": { + "default": { + "type": "css", + "locator": ".myLocator" + }, + "DE": { + "type": "css", + "locator": ".meinLocator" + } + } +} +``` + +You can set the `data.locale` property as follows: + +```js +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](https://github.com/paypal/nemo-view/pull/32) and issue [here](https://github.com/paypal/nemo-view/issues/33). +Follow along as we discuss a backwards compatible way to resolve this unfortunate nomenclature error. \ No newline at end of file diff --git a/lib/locatex.js b/lib/locatex.js index 5c77043..aeb7b8b 100644 --- a/lib/locatex.js +++ b/lib/locatex.js @@ -1,12 +1,9 @@ 'use strict'; module.exports = function (nemo) { - return function locatex(viewJSON, locatorArray) { - var locale = (nemo.data && nemo.data.locale) ? nemo.data.locale : 'default', - locatr = viewJSON; - locatorArray.forEach(function (level) { - locatr = locatr[level]; - }); - return locatr[locale] || locatr['default'] || locatr; + return function locatex(locatorJSON) { + var locale = nemo._config.get('data:locale') || 'default'; + var localizedLocatorJSON = locatorJSON[locale] || locatorJSON['default'] || locatorJSON; + return localizedLocatorJSON; }; }; \ No newline at end of file diff --git a/lib/locreator.js b/lib/locreator.js index 58d0238..d85dd5c 100644 --- a/lib/locreator.js +++ b/lib/locreator.js @@ -3,8 +3,11 @@ var Drivex = require('selenium-drivex'); var Locatex = require('./locatex'); var _ = require('lodash'); var normalize = require('./normalize'); +var debug = require('debug'); +var log = debug('nemo-view:log'); var Locreator = function (nemo) { + log('creating Locreator instance'); this.nemo = nemo; this.drivex = Drivex(nemo.driver, nemo.wd); this.locatex = Locatex(nemo); @@ -13,6 +16,7 @@ var Locreator = function (nemo) { }; }; Locreator.prototype.addGenericMethods = function generics() { + log('adding generic methods'); var normify = this.normify; var drivex = this.drivex; var nemo = this.nemo; @@ -44,56 +48,63 @@ Locreator.prototype.addGenericMethods = function generics() { }; }; -Locreator.prototype.addStarMethods = function locreator(locatorId, _locator, parentWebElement) { +Locreator.prototype.addStarMethods = function addStarMethods(locatorId, locatorJSON, parentWebElement) { + log('add star methods for %s', locatorId); var locatorObject = {}; var drivex = this.drivex; - var locator = this.normify(_locator); + var locreator = this; + var locator = function () { + return locreator.normify(locreator.locatex(locatorJSON)); + }; + + //this is an error check. if an error thrown, invalid locatorJSON. + locator(); locatorObject[locatorId] = function () { - return drivex.find(locator, parentWebElement); + return drivex.find(locator(), parentWebElement); }; locatorObject[locatorId + 'By'] = function () { - return locator; + return locator(); }; locatorObject[locatorId + 'Present'] = function () { - return drivex.present(locator, parentWebElement); + return drivex.present(locator(), parentWebElement); }; locatorObject[locatorId + 'Wait'] = function (timeout, msg) { - return drivex.waitForElementPromise(locator, timeout || 5000, msg); + return drivex.waitForElementPromise(locator(), timeout || 5000, msg); }; locatorObject[locatorId + 'WaitVisible'] = function (timeout, msg) { - return drivex.waitForElementVisiblePromise(locator, timeout || 5000, msg); + return drivex.waitForElementVisiblePromise(locator(), timeout || 5000, msg); }; locatorObject[locatorId + 'Visible'] = function () { - return drivex.visible(locator, parentWebElement); + return drivex.visible(locator(), parentWebElement); }; locatorObject[locatorId + 'OptionText'] = function (optionText) { - return drivex.selectByOptionText(locator, optionText, parentWebElement); + return drivex.selectByOptionText(locator(), optionText, parentWebElement); }; locatorObject[locatorId + 'OptionValue'] = function (optionValue) { - return drivex.selectByOptionValue(locator, optionValue, parentWebElement); + return drivex.selectByOptionValue(locator(), optionValue, parentWebElement); }; locatorObject[locatorId + 'TextEquals'] = function (value) { - return drivex.validateText(locator, parentWebElement, value) ; + return drivex.validateText(locator(), parentWebElement, value) ; }; locatorObject[locatorId + 'AttrEquals'] = function (attribute, value) { - return drivex.validateAttributeValue(locator, parentWebElement, attribute, value); + return drivex.validateAttributeValue(locator(), parentWebElement, attribute, value); }; return locatorObject; }; -Locreator.prototype.addGroup = function (viewJSON, locator, locatorId) { +Locreator.prototype.addGroup = function (locatorId, locatorJSON) { var nemo = this.nemo; var drivex = this.drivex; - var locatex = this.locatex; - var self = this; + var locreator = this; return function () { //give back the nemo.view.viewname.list() function - return drivex.finds(normalize(nemo, locator)).then(function (parentWebElements) { + var localizedJSON = locreator.locatex(locatorJSON); + return drivex.finds(locreator.normify(localizedJSON)).then(function (parentWebElements) { return nemo.wd.promise.map(parentWebElements, function (parentWebElement) { var parentObject = {}; - Object.keys(viewJSON[locatorId].Elements).forEach(function (childLocatorId) { - var childLocator = locatex(viewJSON, [locatorId, 'Elements', childLocatorId]); - var starMethods = self.addStarMethods(childLocatorId, childLocator, parentWebElement); + Object.keys(localizedJSON.Elements).forEach(function (childLocatorId) { + var childLocatorJSON = localizedJSON.Elements[childLocatorId]; + var starMethods = locreator.addStarMethods(childLocatorId, childLocatorJSON, parentWebElement); _.merge(parentObject, starMethods); }); return parentObject; diff --git a/lib/normalize.js b/lib/normalize.js index 61f3ec0..c4f6e7b 100644 --- a/lib/normalize.js +++ b/lib/normalize.js @@ -31,8 +31,10 @@ var _splitLocator = function (nemo, locatorString) { */ module.exports = function normalize(nemo, _locator) { var locator = _locator; + var normalizedLocator; if (_locator.constructor === String) { locator = _splitLocator(nemo, _locator); } - return nemo.wd.By[locator.type](locator.locator); + normalizedLocator = nemo.wd.By[locator.type](locator.locator); + return normalizedLocator; }; \ No newline at end of file diff --git a/lib/view.js b/lib/view.js index ff63444..f5389c3 100644 --- a/lib/view.js +++ b/lib/view.js @@ -28,11 +28,11 @@ var _ = require('lodash'); module.exports = function View(nemo, locreator, viewJSON) { return _.transform(viewJSON, function (_viewObject, n, locatorId) { - var locator = locreator.locatex(viewJSON, [locatorId]); - if (viewJSON[locatorId].Elements) { - _viewObject[locatorId] = locreator.addGroup(viewJSON, locator, locatorId); + var locatorJSON = viewJSON[locatorId]; + if (locreator.locatex(locatorJSON).Elements) { + _viewObject[locatorId] = locreator.addGroup(locatorId, locatorJSON); } else { - _.merge(_viewObject, locreator.addStarMethods(locatorId, locator)); + _.merge(_viewObject, locreator.addStarMethods(locatorId, locatorJSON)); } }); }; diff --git a/package.json b/package.json index 4fc6a87..8f9b5ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nemo-view", - "version": "1.1.0", + "version": "1.2.0", "description": "View Interface for nemo views (requires nemo-drivex and nemo-locatex plugins)", "main": "index.js", "registerAs": "view", diff --git a/test/locale.js b/test/locale.js new file mode 100644 index 0000000..7538619 --- /dev/null +++ b/test/locale.js @@ -0,0 +1,73 @@ +/* global describe,before,after,beforeEach,it */ +'use strict'; + +var Nemo = require('nemo'), + nemo = {}, + path = require('path'), + assert = require('assert'), + util = require(path.resolve(__dirname, 'util')); + +describe('nemo-view @locale@', function () { + before(function (done) { + nemo = Nemo({ + plugins: { + view: { + module: 'path:../', + arguments: ['path:mocks/locale'] + } + } + }, 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('works for standard locators', function (done) { + nemo.view.form.text().getAttribute('id').then(function (idValue) { + assert.equal(idValue, 'foo_text'); + nemo._config.set('data:locale', 'DE'); + }).then(function () { + return nemo.view.form.text().getAttribute('id'); + }).then(function (idValue) { + assert.equal(idValue, 'bar_text'); + nemo._config.set('data:locale', ''); + }).then(function () { + return nemo.view.form.text().getAttribute('id'); + }).then(function (idValue) { + assert.equal(idValue, 'foo_text'); + done(); + }); + }); + it('works for Elements with inner locale scope', function (done) { + nemo.view.form.boxInnerLocale().then(function (elts) { + return elts[0].elt().getAttribute('type').then(function (typeValue) { + assert.equal(typeValue, 'text'); + nemo._config.set('data:locale', 'DE'); + return elts[0].elt().getAttribute('type'); + }); + }).then(function (typeValue) { + assert.equal(typeValue, 'button'); + done(); + }); + }); + it('works for Elements with outer locale scope', function (done) { + nemo._config.set('data:locale', null); + nemo.view.form.boxOuterLocale().then(function (elts) { + elts[0].elt().getAttribute('id').then(function (idValue) { + assert.equal(idValue, 'foo_text'); + return true; + }); + }).then(function (typeValue) { + nemo._config.set('data:locale', 'DE'); + nemo.view.form.boxOuterLocale().then(function (elts) { + elts[0].elt().getAttribute('id').then(function (idValue) { + assert.equal(idValue, 'bar_text'); + done(); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/mocks/locale/form.json b/test/mocks/locale/form.json new file mode 100644 index 0000000..6a78f36 --- /dev/null +++ b/test/mocks/locale/form.json @@ -0,0 +1,38 @@ +{ + "text": { + "default": { + "locator": "#foo input.texty", + "type": "css" + }, + "DE": { + "locator": "#bar input.texty", + "type": "css" + } + }, + "boxInnerLocale": { + "locator": "#foo", + "type": "css", + "Elements": { + "elt": { + "default": "input.texty", + "DE": "[type=button]" + } + } + }, + "boxOuterLocale": { + "default": { + "locator": "#foo", + "type": "css", + "Elements": { + "elt": "input.texty" + } + }, + "DE": { + "locator": "#bar", + "type": "css", + "Elements": { + "elt": "input.texty" + } + } + } +} \ No newline at end of file