Skip to content

Instantly share code, notes, and snippets.

@hs025828
Last active December 21, 2015 01:09
Show Gist options
  • Save hs025828/6225530 to your computer and use it in GitHub Desktop.
Save hs025828/6225530 to your computer and use it in GitHub Desktop.
Appium/Mechanic NULL/Junk element fix.
/*
* mechanic.js UIAutomation Library
* http://cozykozy.com/pages/mechanicjs
*
* Copyright (c) 2012 Jason Kozemczak
* mechanic.js may be freely distributed under the MIT license.
*
* Includes parts of Zepto.js
* Copyright 2010-2012, Thomas Fuchs
* Zepto.js may be freely distributed under the MIT license.
*/
var mechanic = (function() {
// Save a reference to the local target for convenience
var target = UIATarget.localTarget();
// Set the default timeout value to 0 to avoid making walking the object tree incredibly slow.
// Developers can adjust this value by calling $.timeout(duration)
target.setTimeout(0);
var app = target.frontMostApp(),
window = app.mainWindow(),
emptyArray = [],
slice = emptyArray.slice,
idSelectorRE = /^#([\w\s\W-]+)$/;
// Setup a map of UIAElement types to their "shortcut" selectors.
var typeShortcuts = {
'UIAActionSheet' : ['actionsheet'],
'UIAActivityIndicator' : ['activityIndicator'],
'UIAAlert' : ['alert'],
'UIAButton' : ['button'],
'UIACollectionView': ['collection'],
'UIACollectionCell': ['collectionCell'],
'UIAElement' : ['\\*'], // TODO: sort of a hack
'UIAImage' : ['image'],
'UIALink' : ['link'],
'UIAPageIndicator' : ['pageIndicator'],
'UIAPicker' : ['picker'],
'UIAPickerWheel' : ['pickerwheel'],
'UIAPopover' : ['popover'],
'UIAProgressIndicator' : ['progress'],
'UIAScrollView' : ['scrollview'],
'UIASearchBar' : ['searchbar'],
'UIASecureTextField' : ['secure'],
'UIASegmentedControl' : ['segmented'],
'UIASlider' : ['slider'],
'UIAStaticText' : ['text'],
'UIAStatusBar' : ['statusbar'],
'UIASwitch' : ['switch'],
'UIATabBar' : ['tabbar'],
'UIATableView' : ['tableview'],
'UIATableCell' : ['cell', 'tableCell'],
'UIATableGroup' : ['group'],
'UIATextField' : ['textfield'],
'UIATextView' : ['textview'],
'UIAToolbar' : ['toolbar'],
'UIAWebView' : ['webview'],
'UIAWindow' : ['window'],
'UIANavigationBar': ['navigationBar']
};
// Build a RegExp for picking out type selectors.
var typeSelectorRE = (function() {
var typeSelectorREString = "\\";
for (key in typeShortcuts) {
typeSelectorREString += key + "|";
typeShortcuts[key].forEach(function(shortcut) { typeSelectorREString += shortcut + "|" });
}
typeSelectorREString = typeSelectorREString.substr(0, typeSelectorREString.length - 1);
return new RegExp(typeSelectorREString);
})();
// Add functions to UIAElement to make object graph searching easier.
UIAElement.prototype.getElementsByName = function(name) {
var foundEls = [];
$.each(this.elements(), function(idx, el) {
if (el.name() === name) foundEls.push(el);
else foundEls = foundEls.concat(el.getElementsByName(name));
});
return foundEls;
};
UIAElement.prototype.getElementsByType = function(type) {
return $.map(this.elements(), function(el) {
var matches = el.getElementsByType(type);
if (el.isType(type)) matches.unshift(el);
return matches;
});
};
UIAElement.prototype.isType = function(type) {
var thisType = this.toString().split(" ")[1];
thisType = thisType.substr(0, thisType.length - 1);
if (type === thisType) return true;
else if (typeShortcuts[thisType] !== undefined && typeShortcuts[thisType].indexOf(type) >= 0) return true;
else if (type === '*' || type === 'UIAElement') return true;
else return false;
};
function isF(value) { return ({}).toString.call(value) == "[object Function]" }
function isO(value) { return value instanceof Object }
function isA(value) { return value instanceof Array }
function likeArray(obj) { return typeof obj.length == 'number' }
function compact(array) { return array.filter(function(item){ return item !== undefined && item !== null }) }
function flatten(array) { return array.length > 0 ? [].concat.apply([], array) : array }
function uniq(array) { return array.filter(function(item,index,array){ return array.indexOf(item) == index }) }
function Z(dom, selector){
dom = dom || emptyArray;
dom.__proto__ = Z.prototype;
dom.selector = selector || '';
return dom;
}
function $(selector, context) {
if (!selector) return Z();
if (context !== undefined) return $(context).find(selector);
else if (selector instanceof Z) return selector;
else {
var dom;
if (isA(selector)) dom = compact(selector);
else if (selector instanceof UIAElement) dom = [selector]
else dom = $$(app, selector);
return Z(dom, selector);
}
}
$.qsa = $$ = function(element, selector) {
var found;
if (idSelectorRE.test(selector)) {
return element.getElementsByName(selector.substr(1));
} else if (typeSelectorRE.test(selector)) {
found = element.getElementsByType(selector);
return found ? found : emptyArray;
} else {
return emptyArray;
}
};
function filtered(elements, selector) {
return selector === undefined ? $(elements) : $(elements).filter(selector);
}
$.extend = function(target){
slice.call(arguments, 1).forEach(function(source) {
for (key in source) target[key] = source[key];
});
return target;
};
$.inArray = function(elem, array, i) {
return emptyArray.indexOf.call(array, elem, i);
};
$.map = function(elements, callback) {
var value, values = [], i, key;
if (likeArray(elements))
for (i = 0; i < elements.length; i++) {
value = callback(elements[i], i);
if (value != null) values.push(value);
}
else
for (key in elements) {
value = callback(elements[key], key);
if (value != null) values.push(value);
}
return flatten(values);
};
$.each = function(elements, callback) {
var i, key;
if (likeArray(elements))
for(i = 0; i < elements.length; i++) {
if(callback.call(elements[i], i, elements[i]) === false) return elements;
}
else
for(key in elements) {
if(callback.call(elements[key], key, elements[key]) === false) return elements;
}
return elements;
};
$.fn = {
forEach: emptyArray.forEach,
reduce: emptyArray.reduce,
push: emptyArray.push,
indexOf: emptyArray.indexOf,
concat: emptyArray.concat,
map: function(fn){
return $.map(this, function(el, i){ return fn.call(el, i, el) });
},
slice: function(){
return $(slice.apply(this, arguments));
},
get: function(idx){ return idx === undefined ? slice.call(this) : this[idx] },
size: function(){ return this.length },
each: function(callback) {
this.forEach(function(el, idx){ callback.call(el, idx, el) });
return this;
},
filter: function(selector) {
return $([].filter.call(this, function(el) {
var parent = el.parent();
return parent && $$(parent, selector).indexOf(el) >= 0;
}));
},
end: function(){
return this.prevObject || $();
},
andSelf:function(){
return this.add(this.prevObject || $())
},
add:function(selector,context){
return $(uniq(this.concat($(selector,context))));
},
is: function(selector){
return this.length > 0 && $(this[0]).filter(selector).length > 0;
},
not: function(selector){
var nodes=[];
if (isF(selector) && selector.call !== undefined)
this.each(function(idx){
if (!selector.call(this,idx)) nodes.push(this);
});
else {
var excludes = typeof selector == 'string' ? this.filter(selector) :
(likeArray(selector) && isF(selector.item)) ? slice.call(selector) : $(selector);
this.forEach(function(el){
if (excludes.indexOf(el) < 0) nodes.push(el);
});
}
return $(nodes);
},
eq: function(idx){
return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1);
},
first: function(){ var el = this[0]; return el && !isO(el) ? el : $(el) },
last: function(){ var el = this[this.length - 1]; return el && !isO(el) ? el : $(el) },
find: function(selector) {
var result;
if (this.length == 1) result = $$(this[0], selector);
else result = this.map(function(){ return $$(this, selector) });
return $(result);
},
dedup: function() {
var result = this.reduce(function(accum,cur) {
if (accum.indexOf(cur) === -1) {
accum.push(cur);
}
return accum;
}, []);
return $(result);
},
predicate: function(predicate) {
this.map(function(idx, el) {
if (typeof predicate == 'string') {
return el.withPredicate(predicate).toArray();
} else {
}
});
},
valueForKey: function(key, value) {
var result = this.map(function(idx, el) {
if (key in el) {
if (el[key]() == value) {
return el;
}
}
return null;
});
return $(result);
},
valueInKey: function(key, val) {
var result = this.map(function(idx, el) {
if (key in el) {
var elKey = el[key]();
if (elKey === null) {
return null;
}
// make this a case insensitive search
elKey = elKey.toString().toLowerCase();
val = val.toString().toLowerCase();
if (elKey.indexOf(val) !== -1) {
return el;
}
}
return null;
});
return $(result);
},
closest: function(selector, context) {
var el = this[0], candidates = $$(context || app, selector);
if (!candidates.length) el = null;
while (el && candidates.indexOf(el) < 0)
el = el !== context && el !== app && el.parent();
return $(el);
},
ancestry: function(selector) {
var ancestors = [], elements = this;
while (elements.length > 0)
elements = $.map(elements, function(node){
if ((node = node.parent()) && !node.isType('UIAApplication') && ancestors.indexOf(node) < 0) {
ancestors.push(node);
return node;
}
});
return filtered(ancestors, selector);
},
parent: function(selector) {
return filtered(uniq(this.map(function() { return this.parent() })), selector);
},
children: function(selector) {
return filtered(this.map(function(){ return slice.call(this.elements()) }), selector);
},
childrenByType: function(type) {
var result = this.map(function() {
var children = this.elements();
var filtered = [];
for (var i = 0; i < children.length; i++) {
if (children[i].isType(type)) {
filtered.push(children[i]);
}
}
return filtered;
});
return $(result);
},
siblings: function(selector) {
return filtered(this.map(function(i, el) {
return slice.call(el.parent().elements()).filter(function(child){ return child!==el });
}), selector);
},
index: function(element) {
return element ? this.indexOf($(element)[0]) : this.parent().elements().toArray().indexOf(this[0]);
},
pluck: function(property) { return this.map(function(){ return this[property] }) }
};
'filter,add,not,eq,first,last,find,closest,parents,parent,children,siblings'.split(',').forEach(function(property) {
var fn = $.fn[property];
$.fn[property] = function() {
var ret = fn.apply(this, arguments);
ret.prevObject = this;
return ret;
};
});
Z.prototype = $.fn;
return $;
})();
var $ = $ || mechanic; // expose $ shortcut
// mechanic.js
// Copyright (c) 2012 Jason Kozemczak
// mechanic.js may be freely distributed under the MIT license.
(function($) {
$.extend($, {
log: function(s, level) {
level = level || 'message';
if (level === 'error') UIALogger.logError(s);
else if (level === 'warn') UIALogger.logWarning(s);
else if (level === 'debug') UIALogger.logDebug(s);
else UIALogger.logMessage(s);
},
error: function(s) { $.log(s, 'error'); },
warn: function(s) { $.log(s, 'warn'); },
debug: function(s) { $.log(s, 'debug'); },
message: function(s) { $.log(s, 'message'); },
capture: function(imageName, rect) {
imageName = imageName || new Date().toString();
if (rect) target.captureRectWithName(rect, imageName);
else target.captureScreenWithName(imageName);
}
});
$.extend($.fn, {
log: function() { return this.each(function() { this.logElement(); }); },
logTree: function () { return this.each(function() { this.logElementTree(); }); },
capture: function(imageName) {
imageName = imageName || new Date().toString();
return this.each(function() { $.capture(imageName + '-' + this.name(), this.rect()); });
}
});
})(mechanic);
// mechanic.js
// Copyright (c) 2012 Jason Kozemczak
// mechanic.js may be freely distributed under the MIT license.
(function($) {
var app = UIATarget.localTarget().frontMostApp();
$.extend($.fn, {
name: function() { return (this.length > 0) ? this[0].name() : null; },
label: function() { return (this.length > 0) ? this[0].label() : null; },
value: function() { return (this.length > 0) ? this[0].value() : null; },
isFocused: function() { return (this.length > 0) ? this[0].hasKeyboardFocus() : false; },
isVisible: function() { return (this.length > 0) ? this[0].isVisible() === 1 : false; },
isValid: function(certain) {
if (this.length > 0) return false;
else if (certain) return this[0].checkIsValid();
else return this[0].isValid();
}
});
$.extend($, {
version: function() {
return app.version();
},
bundleId: function() {
return app.bundleID();
},
prefs: function(prefsOrKey) {
// TODO: should we handle no-arg version that returns all prefs???
if (typeof prefsToReturn == 'string') return app.preferencesValueForKey();
else {
$.each(prefsOrKey, function(val, key) {
app.setPreferencesValueForKey(val, key);
});
}
}
});
})(mechanic);
// mechanic.js
// Copyright (c) 2012 Jason Kozemczak
// mechanic.js may be freely distributed under the MIT license.
(function($) {
var target = UIATarget.localTarget();
$.extend($, {
timeout: function(duration) { target.setTimeout(duration); },
delay: function(seconds) { target.delay(seconds); },
cmd: function(path, args, timeout) { target.host().performTaskWithPathArgumentsTimeout(path, args, timeout); },
orientation: function(orientation) {
if (orientation === undefined || orientation === null) return target.deviceOrientation();
else target.setDeviceOrientation(orientation);
},
location: function(coordinates, options) {
options = options || {};
target.setLocationWithOptions(options);
},
shake: function() { target.shake(); },
rotate: function(options) { target.rotateWithOptions(options); },
pinchScreen: function(options) {
if (!options.style) options.style = 'open';
if (options.style === 'close') target.pinchCloseFromToForDuration(options.from, options.to, options.duration);
else target.pinchOpenFromToForDuration(options.from, options.to, options.duration);
},
drag: function(options) { target.dragFromToForDuration(options.from, options.to, options.duration); },
flick: function(options) { target.flickFromTo(options.from, options.to); },
lock: function(duration) { target.lockForDuration(duration); },
backgroundApp: function(duration) { target.deactivateAppForDuration(duration); },
volume: function(direction, duration) {
if (direction === 'up') {
if (duration) target.holdVolumeUp(duration)
else target.clickVolumeUp();
} else {
if (duration) target.holdVolumeDown(duration);
else target.clickVolumeDown();
}
},
input: function(s) {
target.frontMostApp().keyboard().typeString(s);
}
});
$.extend($.fn, {
tap: function(options) {
options = options || {};
return this.each(function() {
// TODO: tapWithOptions supports most of the behavior of doubleTap/twoFingerTap looking at the API, do we need to support these methods??
if (options.style === 'double') this.doubleTap();
else if (options.style === 'twoFinger') this.twoFingerTap();
else this.tapWithOptions(options);
});
},
touch: function(duration) {
return this.each(function() { this.touchAndHold(duration); });
},
dragInside: function(options) {
return this.each(function() { this.dragInsideWithOptions(options); });
},
flick: function(options) {
return this.each(function() { this.flickInsideWithOptions(options); });
},
rotate: function(options) {
return this.each(function() { this.rotateWithOptions(options); });
},
scrollToVisible: function() {
if (this.length > 0) this[0].scrollToVisible();
return this;
},
input: function(s) {
if (this.length > 0) {
this[0].tap();
$.input(s);
}
}
});
'delay,cmd,orientation,location,shake,pinchScreen,drag,lock,backgroundApp,volume'.split(',').forEach(function(property) {
var fn = $[property];
$.fn[property] = function() {
fn.apply($, arguments);
return this;
};
});
})(mechanic);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment