Skip to content

Instantly share code, notes, and snippets.

@hs025828
Last active December 21, 2015 01:09

Revisions

  1. hs025828 revised this gist Aug 13, 2013. 1 changed file with 0 additions and 3 deletions.
    3 changes: 0 additions & 3 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -32,11 +32,8 @@ var mechanic = (function() {
    'UIAButton' : ['button'],
    'UIACollectionView': ['collection'],
    'UIACollectionCell': ['collectionCell'],
    'UIAEditingMenu': ['editingMenu'],
    'UIAElement' : ['\\*'], // TODO: sort of a hack
    'UIAImage' : ['image'],
    'UIAKey' : ['key'],
    'UIAKeyboard' : ['keyboard'],
    'UIALink' : ['link'],
    'UIAPageIndicator' : ['pageIndicator'],
    'UIAPicker' : ['picker'],
  2. hs025828 created this gist Aug 13, 2013.
    514 changes: 514 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,514 @@
    /*
    * 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'],
    'UIAEditingMenu': ['editingMenu'],
    'UIAElement' : ['\\*'], // TODO: sort of a hack
    'UIAImage' : ['image'],
    'UIAKey' : ['key'],
    'UIAKeyboard' : ['keyboard'],
    '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);