protractor-pom-generator
Version:
A small tool use for generating page object model for protractor.
927 lines (762 loc) • 30.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Generator = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _common = require('./common');
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var LETTERS = new _common.Common().LETTERS;
var VISIBILITIES = new _common.Common().VISIBILITIES;
var ELEMENT_NODE = 1;
var NG_PREFIXES = ['ng-', 'data-ng-', 'ng_', 'x-ng-', 'ng\\:'];
var NG_STRATEGIES = [{ handler: getNgModelName, strategy: 'model' }];
var SHOW_COMMENT = 128;
var Generator = exports.Generator = function () {
function Generator() {
_classCallCheck(this, Generator);
this.LETTERS = LETTERS;
this.VISIBILITIES = VISIBILITIES;
}
_createClass(Generator, [{
key: 'generate',
value: function generate(window, input) {
var common = new _common.Common();
input = input || {};
var output = Object.extend(input);
output = common.setDefaultValues(output);
output = setDefinitions(window, output);
output.url = window.document.location.href;
return output;
}
// ========================================================================
// POG namespace
}]);
return Generator;
}();
// ========================================================================
// private functions
function getAttributeSelector(document, name, node) {
var response = '';
var value = node.getAttribute(name);
if (value) {
var selector = node.nodeName.toLowerCase();
if (name === 'class') {
selector += '.' + value.trim().split(/\s+/g).join('.');
} else {
value = value.replace(/\r?\n|\r/g, '');
selector += '[' + name + '=\'' + value + '\']';
}
try {
var elements = document.querySelectorAll(selector);
if (elements.length === 1) {
response = selector;
}
} catch (ex) {
// bad selector. skipping.
}
}
return response;
}
function getClosestSibling(node, siblings) {
var copies = [].slice.call(siblings);
copies.unshift(node);
var copiesLength = copies.length;
var closest = 1;
if (closest > copiesLength) {
closest = 0;
}
var nodeIndex = 0;
var siblingIndex = closest;
for (var i = 0; i < copiesLength; i++) {
var delta = Math.abs(nodeIndex - i);
if (delta > closest) {
closest = delta;
siblingIndex = i;
}
}
return siblingIndex === 0 ? null : copies[siblingIndex];
}
function getComments(document, root) {
var comments = [];
var index = -1;
var walker = document.createTreeWalker(root, SHOW_COMMENT, null, false);
while (walker.nextNode()) {
comments[++index] = walker.currentNode;
}
return comments;
}
function getCSSSelector(node) {
var selector = '';
for (; node && node.nodeType === ELEMENT_NODE; node = node.parentNode) {
if (node && node.id) {
selector = '#' + node.id + ' ' + selector;
break;
} else {
var nodeName = node.nodeName;
var currentSelector = nodeName.toLowerCase();
var index = getNodeIndex(node, nodeName);
if (index > 0) {
// it has siblings
currentSelector += ':nth-of-type(' + index + ')';
} else {
if (node.className !== '') {
currentSelector += '.' + node.className.split(/\s+/g).join('.');
}
if (nodeName === 'INPUT') {
if (node.getAttribute('type')) {
currentSelector += '[type=\'' + node.type + '\']';
} else if (node.getAttribute('data-type')) {
currentSelector += '[data-type=\'' + node.getAttribute('data-type') + '\']';
}
}
}
selector = currentSelector + ' ' + selector;
}
}
return selector.replace(/^html[^\b]*\bbody\b/, '').trim();
}
function getValidVariableName(name) {
if (name.length && !/[$_a-zA-Z]/.test(name[0])) {
return '_' + name;
}
return name;
}
function getDefinition(input) {
input = input || {};
var actionLowered = input.action.toLowerCase();
var buffer = Object.extend(input.buffer);
// deep copy
buffer.attribute = Object.extend(buffer.attribute);
buffer.operation = Object.extend(buffer.operation);
var suffixes = {
action: actionLowered === 'click' ? ' on' : '',
label: actionLowered === 'set' ? ' Field' : ''
};
suffixes.documentation = ' ' + getLetter(input.fullText || input.text, LETTERS.NATURAL) + ' ' + (input.label + suffixes.label.toLowerCase()) + '.';
suffixes.name = ' ' + input.text + ' ' + input.label + suffixes.label;
if (input.negate) {
input.action = 'Un' + actionLowered;
buffer.negate = 1;
}
if (input.hasArgument) {
suffixes.action = input.negate ? ' value from' : ' value to';
buffer.argument = {};
buffer.argument.documentation = input.action + ' default' + suffixes.action + suffixes.documentation;
buffer.argument.key = getLetter(input.text, LETTERS.UPPER);
buffer.argument.name = getLetter(input.text + ' value', input.letters.attribute);
}
buffer.attribute.name = getValidVariableName(getLetter(input.text, input.letters.attribute));
buffer.operation.documentation = input.action + suffixes.action + suffixes.documentation;
buffer.operation.name = getLetter(input.action + suffixes.name, input.letters.operation, input.action);
return buffer;
}
function getFileName(path) {
return path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.'));
}
function getHiddens(cloned, original) {
var clones = cloned.getElementsByTagName('*');
var originals = original.getElementsByTagName('*');
var hiddens = cloned ? Array.filter(cloned.querySelectorAll('*:not(br):not(img):not(input):not(link):not(option):not(script):not(select):not(style)'), function (item, index) {
var sourceIndex = [].indexOf.call(clones, item);
return originals[sourceIndex].offsetHeight < 1 || !isElementInViewport(window, item);
}) : [];
return hiddens;
}
function getLabelText(document, node) {
var text = '';
if (node && node.id) {
text = getLabelTextFor(document, node, 'id');
}
if (text === '' && node && node.name) {
// non-standard, but it happens
text = getLabelTextFor(document, node, 'name');
}
if (text === '') {
// find label from siblings
// TODO: should use more aggressive collector
var labels = node && node.parentNode ? node.parentNode.querySelectorAll('label') : [];
var label = getClosestSibling(node, labels);
if (label) {
text = label.textContent || label.innerText || '';
text = text.trim();
}
}
return text;
}
function getLabelTextFor(document, node, attribute) {
var identifier = node.getAttribute(attribute) || node[attribute] || '';
var text = '';
if (identifier) {
var label = document.querySelector('label[for="' + identifier + '"]');
if (label) {
text = label.textContent || label.innerText || '';
text = text.trim();
}
if (text === '') {
var identifierLowered = identifier.toLowerCase();
var labels = Array.filter(document.querySelectorAll('label[for]'), function (item) {
return item.getAttribute('for').toLowerCase() === identifierLowered;
});
label = getClosestSibling(node, labels);
if (label) {
text = label.textContent || label.innerText || '';
text = text.trim();
}
}
}
return text;
}
function getLetter(value, type, action) {
action = action || '';
type = type || LETTERS.CAMEL;
type = parseInt(type);
value = value || '';
if (type !== LETTERS.NATURAL) {
// move number prefix to the end of the value
var oldValue = value.replace(action, '').trim();
var numberPrefix = /^([\d.]+)/.exec(oldValue);
if (numberPrefix) {
value = value.replace(numberPrefix[0], '') + ' ' + numberPrefix[0];
}
}
switch (type) {
case LETTERS.LOWER:
case LETTERS.UPPER:
value = value.replace(/\./g, '_').replace(/\s+|__/g, '_').replace(/^_|_$/g, '');
value = type === LETTERS.LOWER ? value.toLowerCase() : value.toUpperCase();
break;
case LETTERS.CAMEL:
case LETTERS.PROPER:
value = value.replace(/\./g, ' ').trim().replace(/\s\s+/g, ' ').replace(/\w\S*/g, function (word) {
return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
}).replace(/\s+/g, '');
if (type === LETTERS.CAMEL) {
value = value.charAt(0).toLowerCase() + value.substr(1);
}
break;
case LETTERS.NATURAL:
value = value.trim().replace(/\s\s+/g, ' ').replace(/\w\S*/g, function (word) {
return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
});
break;
}
return value;
}
function getLinkText(node) {
var image = node.querySelector('img');
var text = '';
if (image) {
text = image.alt || getFileName(image.src);
}
return text.trim();
}
function getLocator(document, node, angular) {
var response = {};
if (angular) {
response = getNgLocator(node);
}
if (!response.strategy) {
if (node.id) {
response.strategy = 'id';
response.value = node.id;
} else if (node.name) {
response.strategy = 'name';
response.value = node.name;
} else {
response.strategy = 'css';
if (node.getAttribute('class')) {
response.value = getAttributeSelector(document, 'class', node);
}
if (!response.value && node.title) {
response.value = getAttributeSelector(document, 'title', node);
}
if (!response.value && node.getAttribute('href')) {
response.value = getAttributeSelector(document, 'href', node);
}
if (!response.value) {
response.value = getCSSSelector(node);
}
}
}
return response;
}
function getLongestName(name, longest) {
name = name || '';
var length = name.length;
if (length > longest) {
longest = length;
}
return longest;
}
function getNgLocator(node) {
var response = {};
for (var i = 0, j = NG_STRATEGIES.length; i < j; i++) {
var item = NG_STRATEGIES[i];
var value = item.handler(node);
if (value) {
response.strategy = item.strategy;
response.value = value;
}
}
return response;
}
function getNgModelName(node) {
var name = '';
for (var i = 0, j = NG_PREFIXES.length; i < j; i++) {
name = node.getAttribute(NG_PREFIXES[i] + 'model') || '';
if (name) {
break;
}
}
return name.trim();
}
function getNodeIndex(node, nodeName) {
nodeName = nodeName || node.nodeName;
var siblings = node && node.parentNode ? Array.filter(node.parentNode.children, function (item, index) {
return item.nodeName === nodeName;
}) : [];
var index = [].indexOf.call(siblings, node);
// convert to 1-based index
index++;
return index === 1 && index === siblings.length ? 0 : index;
}
function getNodeText(document, node) {
var text = getLabelText(document, node);
if (text === '') {
var parentNode = node ? node.parentNode : null;
if (parentNode) {
var clonedParentNode = parentNode.cloneNode(true);
var clonedNode = clonedParentNode.querySelector(node.nodeName.toLowerCase());
clonedNode.parentNode.removeChild(clonedNode);
clonedParentNode = sanitizeNode(clonedParentNode, parentNode);
text = clonedParentNode.textContent || clonedParentNode.innerText || '';
text = getSentences(text.trim())[0] || '';
}
}
if (text === '') {
text = node ? getNodeText(node.parentNode) : '';
}
return text;
}
function getPageVisibleHTML(document, original) {
original = original || document.body;
var cloned = original.cloneNode(true);
cloned = sanitizeNode(document, cloned, original);
return cloned.outerHTML;
}
function getSanitizedText(text, max) {
var texts = (text || '').split(/\s+/g);
if (max) {
texts = texts.slice(0, max);
}
return texts.join(' ').trim().replace(/[^a-zA-Z0-9\. ]/g, '');
}
function getSentences(text, minimumWords) {
minimumWords = minimumWords || 5;
var index = -1;
var response = [];
var sentences = text.match(/[^\r\n.!?]+/gi) || [];
for (var i = 0, j = sentences.length; i < j; i++) {
var sentence = sentences[i].trim();
if (sentence !== '') {
var words = (sentence.match(/\b\w+\b/gi) || []).length;
if (words >= minimumWords) {
// faster array push
response[++index] = sentence;
}
}
}
return response;
}
function getSentenceFrequency(sentences, words) {
if ({}.toString.call(sentences) !== '[object Array]') {
return [];
}
var items = words.tops.length ? words.tops : words;
sentences = sentences.slice(0);
sentences.frequencies = {};
for (var i = 0, j = sentences.length; i < j; i++) {
var sentence = sentences[i];
var sentenceLowered = sentence.toLowerCase();
sentences.frequencies[sentence] = 0;
for (var k = 0, l = items.length; k < l; k++) {
if (sentenceLowered.indexOf(items[k]) > -1) {
sentences.frequencies[sentence]++;
}
}
}
// desc
sentences.sort(function (a, b) {
return sentences.frequencies[b] - sentences.frequencies[a];
});
return sentences;
}
function getWordFrequency(text) {
var index = -1;
var topIndex = -1;
var words = [];
words.frequencies = {};
words.tops = [];
text.toLowerCase().split(/[\s*\.*\,\;\+?\#\|:\-\/\\\[\]\(\)\{\}$%&0-9*]/).map(function (k, v) {
if (k && k.length > 1) {
words.frequencies[k]++ || (words.frequencies[k] = 1);
}
});
for (var word in words.frequencies) {
words[++index] = word;
if (words.frequencies[word] > 1) {
words.tops[++topIndex] = word;
}
}
// desc
words.sort(function (a, b) {
return words.frequencies[b] - words.frequencies[a];
});
words.tops.sort(function (a, b) {
return words.frequencies[b] - words.frequencies[a];
});
return words;
}
function isElementInViewport(window, el) {
if (typeof jQuery !== 'undefined' && el instanceof jQuery) {
el = el[0];
}
var rect = el.getBoundingClientRect();
var windowHeight = window.innerHeight || document.documentElement.clientHeight;
var windowWidth = window.innerWidth || document.documentElement.clientWidth;
return rect.left > -1 && rect.top > -1 && rect.left + rect.width <= windowWidth && rect.top + rect.height <= windowHeight;
}
function removeNodes(nodes) {
var type = {}.toString.call(nodes);
if (type !== '[object Array]' && !(type === '[object NodeList]' || type === '[object Object]')) {
return;
}
var index = -1;
var length = nodes.length;
while (++index < length) {
var node = nodes[index];
if (node) {
(node.parentNode || {
removeChild: function removeChild() {}
}).removeChild(node);
}
}
}
function sanitizeNode(document, clonedNode, originalNode) {
var comments = getComments(document, clonedNode);
var excludes = clonedNode ? clonedNode.querySelectorAll('img,input,link,option,script,select,style') : [];
var hiddens = getHiddens(clonedNode, originalNode);
removeNodes(comments);
removeNodes(excludes);
var excludedNode = clonedNode.cloneNode(true);
removeNodes(hiddens);
// ng:view template doesn't have height,
// hence it will considered as hidden
if ((clonedNode.textContent || '').trim() === '') {
clonedNode = excludedNode;
}
return clonedNode;
}
function setDefinitions(window, input) {
var document = window.document;
var definitions = [];
var root = document.querySelector(input.nodes.root) || this.document;
//var root = document;
var nodes = root ? root.querySelectorAll(input.nodes.selector) : [];
var type = {}.toString.call(nodes);
if (!(type === '[object NodeList]' || type === '[object Object]')) {
input.definitions = definitions;
return input;
}
var firsts = {};
var hasField = false;
var index = -1;
var longestName = 0;
var submit = { label: '', text: '' };
var tags = document.getElementsByTagName('*');
var texts = {};
var unsets = {};
var visibleOnly = parseInt(input.nodes.visibility) === VISIBILITIES.VISIBLE;
for (var i = 0, j = nodes.length; i < j; i++) {
var buffer = { attribute: {}, operation: {} };
var definition = {};
var node = nodes[i];
if (visibleOnly && node.offsetHeight > 0 || !visibleOnly) {
var action = '';
var hasArgument = false;
var hasUnset = false;
var label = '';
var locator = getLocator(document, node, input.nodes.angular);
var text = node.textContent || node.innerText || '';
buffer.attribute.strategy = locator.strategy;
buffer.attribute.value = locator.value;
buffer.sourceIndex = node.sourceIndex || [].indexOf.call(tags, node);
switch (node.nodeName) {
case 'A':
action = 'Click';
buffer.type = 'link';
label = 'Link';
text = text || getLinkText(node);
if (submit.text === '' && text.toLowerCase().indexOf('submit') > -1) {
submit.label = label;
submit.text = text;
}
break;
case 'BUTTON':
action = 'Click';
buffer.type = 'button';
label = 'Button';
if (submit.text === '' && ((node.type || '').toLowerCase() === 'submit' || text.toLowerCase().indexOf('submit') > -1)) {
submit.label = label;
submit.text = text;
}
break;
case 'INPUT':
var inputType = node.type || '';
if ('|button|image|submit|'.indexOf('|' + inputType + '|') > -1) {
action = 'Click';
buffer.type = 'button';
label = 'Button';
text = text || node.value || getNodeText(node);
if (inputType === 'submit') {
submit.label = label;
submit.text = text;
} else if (submit.text === '' && text.toLowerCase().indexOf('submit') > -1) {
submit.label = label;
submit.text = text;
}
} else {
if (inputType === 'hidden') {
break;
} else if (inputType === 'checkbox') {
hasUnset = true;
} else if ('|email|number|password|radio|search|tel|text|url|'.indexOf('|' + inputType + '|') > -1) {
hasArgument = true;
}
label = getLetter(inputType, LETTERS.PROPER);
text = text || getNodeText(node);
if (inputType === 'radio') {
label = 'Radio Button';
if (buffer.attribute.strategy !== 'name' && node.name) {
buffer.attribute.strategy = 'name';
buffer.attribute.value = node.name;
}
var radioValueBuffer = {
attribute: {
name: getLetter(getSanitizedText(text, 6) + ' Value', input.attributes.letter),
value: node.value
},
operation: {},
sourceIndex: -1,
type: 'radio.value'
};
// faster array push
definitions[++index] = radioValueBuffer;
longestName = getLongestName(radioValueBuffer.attribute.name, longestName);
}
if ('|email|number|password|search|tel|url|'.indexOf('|' + inputType + '|') > -1) {
inputType = 'text';
}
action = 'Set';
buffer.type = inputType;
}
break;
case 'SELECT':
action = 'Set';
buffer.type = 'select';
hasArgument = true;
hasUnset = true;
label = 'Drop Down List';
text = getNodeText(node);
break;
case 'TEXTAREA':
action = 'Set';
buffer.type = 'text';
hasArgument = true;
label = 'Textarea';
text = getNodeText(node);
break;
}
var fullText = getSanitizedText(text);
text = getSanitizedText(text, 6);
if (text !== '') {
if (texts[text]) {
texts[text]++;
if (texts[text] === 2) {
var firstText = text + ' 1';
// need to adjust the first entry and make it as part of the group
definition = getDefinition({
action: action,
buffer: definitions[firsts[text]],
fullText: fullText,
hasArgument: hasArgument,
label: label,
letters: {
attribute: input.attributes.letter,
operation: input.operations.letter
},
text: firstText
});
definitions[firsts[text]] = definition;
if (hasUnset) {
definition = getDefinition({
action: action,
buffer: definitions[unsets[text]],
fullText: fullText,
hasArgument: hasArgument,
label: label,
letters: {
attribute: input.attributes.letter,
operation: input.operations.letter
},
negate: hasUnset,
text: firstText
});
definitions[unsets[text]] = definition;
}
}
text = text + ' ' + texts[text];
} else {
firsts[text] = index + 1;
texts[text] = 1;
if (hasUnset) {
unsets[text] = index + 2;
}
}
definition = getDefinition({
action: action,
buffer: buffer,
fullText: fullText,
hasArgument: hasArgument,
label: label,
letters: {
attribute: input.attributes.letter,
operation: input.operations.letter
},
text: text
});
// faster array push
definitions[++index] = definition;
longestName = getLongestName(definition.attribute.name, longestName);
if (hasUnset) {
definition = getDefinition({
action: action,
buffer: buffer,
fullText: fullText,
hasArgument: hasArgument,
label: label,
letters: {
attribute: input.attributes.letter,
operation: input.operations.letter
},
negate: hasUnset,
text: text
});
// faster array push
definitions[++index] = definition;
}
if (!hasField && action === 'Set') {
hasField = true;
}
}
}
}
// operation extras
if (hasField && input.operations.extras.fill) {
var buffer = {
attribute: {},
operation: {
documentation: 'Fill every fields in the page.',
name: getLetter('Fill', input.operations.letter)
},
negate: true,
sourceIndex: -1,
type: 'fill'
};
// faster array push
definitions[++index] = buffer;
}
if (hasField && submit.text !== '' && input.operations.extras['fill.submit']) {
var buffer = {
attribute: {},
operation: {
documentation: 'Fill every fields in the page and submit it to target page.',
name: getLetter('Fill And Submit', input.operations.letter)
},
negate: true,
sourceIndex: -1,
target: {
modelName: input.model.target
},
type: 'fill.submit'
};
// faster array push
definitions[++index] = buffer;
}
if (submit.text !== '' && input.operations.extras.submit) {
var buffer = {
attribute: {},
operation: {
documentation: 'Submit the form to target page.',
name: getLetter('Submit', input.operations.letter)
},
negate: true,
sourceIndex: -1,
target: {
modelName: input.model.target,
name: getLetter('Click ' + getSanitizedText(submit.text) + ' ' + submit.label, input.operations.letter)
},
type: 'submit'
};
// faster array push
definitions[++index] = buffer;
}
if (input.operations.extras['verify.loaded']) {
var sourceText = getPageVisibleHTML(document);
sourceText = sourceText.replace(/(<([^>]+)>)/gi, '\n');
var sentences = getSentences(sourceText);
var words = getWordFrequency(sourceText);
sentences = getSentenceFrequency(sentences, words);
var sentence = sentences[0] || '';
// !robot
if (input.attributes.letter !== LETTERS.LOWER && input.attributes.indent !== 1 && input.attributes.separator !== '') {
sentence = sentence.replace(/"/g, '\\"');
}
var buffer = {
attribute: {
name: getLetter('Page Loaded Text', input.attributes.letter),
value: sentence
},
operation: {
documentation: 'Verify that the page loaded completely.',
name: getLetter('Verify Page Loaded', input.operations.letter)
},
sourceIndex: -1,
type: 'verify.loaded'
};
// faster array push
definitions[++index] = buffer;
}
if (input.operations.extras['verify.url']) {
// it's better to generate more information than less
var uri = document.location.href.replace(document.location.origin, '');
var buffer = {
attribute: {
name: getLetter('Page Url', input.attributes.letter),
value: uri
},
operation: {
documentation: 'Verify that current page URL matches the expected URL.',
name: getLetter('Verify Page Url', input.operations.letter)
},
sourceIndex: -1,
type: 'verify.url'
};
// faster array push
definitions[++index] = buffer;
}
input.attributes.longestName = longestName;
input.definitions = definitions;
return input;
}