All Downloads are FREE. Search and download functionalities are using the official Maven repository.

instruments-js.UIAElement.js Maven / Gradle / Ivy

The newest version!
/**
 * @module ios-driver
 */

/**
 * returns the class for the element, as per http://developer.apple.com/library/ios/#documentation/DeveloperTools/Reference/UIAutomationRef
 * for instance : UIAElement , UIAKeyboard etc.
 * @return {string} element.
 */
UIAElement.prototype.type = function () {
    return this.toString().replace('[object ', '').replace(']', '');
}

UIAElement.prototype.rect_original = UIAElement.prototype.rect;
/**
 * Similar to rect(), but all dimensions rounded to integers.
 * @return {UIARect} return the position and dimension of the current element.
 */
UIAElement.prototype.rect = function () {
    var rect = this.rect_original();

    rect.origin.x = Math.floor(rect.origin.x);
    rect.origin.y = Math.floor(rect.origin.y);
    rect.size.height = Math.floor(rect.size.height);
    rect.size.width = Math.floor(rect.size.width);
    return rect;
}

/**
 * enter text / password in the input fields.
 * @param value  the text to enter.
 * @param opt_native true -> use the keyboard, otherwise calls setValue() and inject the value.
 */
UIAElement.prototype.sendKeys = function (value, opt_native) {

    var nativeMode = false;
    if (opt_native) {
        nativeMode = true;
    }

    if (nativeMode === true) {
        if (this.hasKeyboardFocus() !== 1) {
            this.tap();
            if (!this.hasKeyboardFocus()) {
                throw new UIAutomationException("Keyboard failed to appear.", 11);
            }
        }
        var app = UIATarget.localTarget().frontMostApp();
        var keyboard = app.keyboard(0);
        keyboard.typeString(value);

    } else {
        this.setValue(value);
    }

}

/**
 * checks is the element is part of an alert.
 * @return {boolean} true is the element is a descendant of an alert.
 */
UIAElement.prototype.isInAlert = function () {
    var parent;
    if (this.parent) {
        parent = this.parent();
    }

    while (parent.type() != "UIAElementNil" && parent.type() != "UIATarget") {
        if (parent.type() == "UIAAlert") {
            return true;
        }
        parent = parent.parent();
    }

    return false;
}

/**
 * checks is the element is part of an action sheet.
 * @return {boolean} true is the element is a descendant of an action sheet.
 */
UIAElement.prototype.isInActionSheet = function () {
    var parent;
    if (this.parent) {
        parent = this.parent();
    }

    while (parent.type() != "UIAElementNil" && parent.type() != "UIATarget") {
        if (parent.type() == "UIAActionSheet") {
            return true;
        }
        parent = parent.parent();
    }

    return false;
}

/**
 * with IOS6, scrollToVisible crashes for elements that are not in a tableView or a webView.
 * As scrollToVisible is used internally by ios-driver to find if an element is stale, it is necessary
 * to workaround this bug.
 * @return {boolean} true if it is safe to call scrollToVisible on the element.
 */
UIAElement.prototype.isScrollable = function () {
    var parent;
    if (this.parent) {
        parent = this.parent();
    }

    while (parent.type() != "UIAElementNil" && parent.type() != "UIATarget") {
        if (parent.type() == "UIATableView" || parent.type() == "UIAWebView") {
            // log(this.type() + "isScollable : " + false);
            return true;
        }
        parent = parent.parent();
    }
    return false;
}

UIAElement.prototype.tap_original = UIAElement.prototype.tap;

/**
 * Tap an an element. By default tap the center of the element, unless opt_XFactor and opt_YFactor are
 * specified.
 * @param {number} opt_XFactor where in the element. 0 is left border, 1 right border
 * @param {number} opt_YFactor where in the element. 0 is top border, 1 is bottom one.
 */
UIAElement.prototype.tap = function (opt_XFactor, opt_YFactor) {

    var xFactor = 0.5;
    var yFactor = 0.5;

    if (opt_XFactor) {
        xFactor = opt_XFactor;
    }

    if (opt_YFactor) {
        yFactor = opt_YFactor;
    }
    if (this.isVisible()) {
        var rect = this.rect();
        var x = rect.origin.x + (rect.size.width * xFactor);
        var y = rect.origin.y + (rect.size.height * yFactor);
        var point = {
            'x': Math.floor(x),
            'y': Math.floor(y)
        };

        UIATarget.localTarget().tap(point);
        UIATarget.localTarget().delay(0.1);
        var newScreen = !this.isVisible();
        if (newScreen) {
            log("wait for transition.");
            //UIATarget.localTarget().delay(1);
        }

        if (this.isInAlert() && this.type() == "UIAButton") {
            log("tapping in something in an alert.Alert is gone.");
            UIAutomation.cache.clearAlert();
        }
    } else {
        var ex = new UIAutomationException("element is not visible.", 11);
        throw ex;
    }

}

/**
 * ios-driver made id. Used to store and retrieve elements from the cache, and represent the element
 * when passed around the network.
 * @return {number} the unique reference of that element. Used by the cache as id.
 */
UIAElement.prototype.reference = function () {
    if (!this.id) {
        UIAutomation.cache.store(this);
    }
    return this.id;
}

UIAElement.prototype.isVisibleOriginal = UIAElement.prototype.isVisible;

/**
 * similar to the original isVisible, but returning a boolean instead of an number.
 * @return {boolean}
 */
UIAElement.prototype.isVisible = function () {
    if (this.isVisibleOriginal()) {
        return true;
    } else {
        return false;
    }
};

UIAElement.prototype.isEnabledOriginal = UIAElement.prototype.isEnabled;

/**
 * similar to the original isEnabled, but returning a boolean instead of an number.
 * @return {boolean}
 */
UIAElement.prototype.isEnabled = function () {

    if (this.isEnabledOriginal()) {
        return true;
    } else {
        return false;
    }
}

UIAElement.prototype.scrollToVisible_original = UIAElement.prototype.scrollToVisible;
/**
 * scrollToVisible only makes sense if the element if in a webview or a
 * tableView. It was working, and doing nothing for other elements up to ios5.1.
 * Starting from ios6, it now throws : Unexpected error in
 * -[UIAStaticText_0xdc363d0 scrollToVisible],
 * /SourceCache/UIAutomation_Sim/UIAutomation-271/Framework/UIAElement.m line
 * 1545, kAXErrorFailure so need to check first if scrolling will do anything to
 * avoid this exception.
 */
UIAElement.prototype.scrollToVisible = function () {
    if (this.isScrollable()) {
        this.scrollToVisible_original();
    }
}
/**
 * can't find a way to detect stale object. CheckIsValid doesn't do it properly.
 * Trying to scroll to the element seems like a valid approximation.
 */
UIAElement.prototype.isStale = function () {
    if (this.checkIsValid() == false) {
        return true;
    } else {
        try {
            this.scrollToVisible();
            // tmp fix for safari and big web views.
            if (this.type() === "UIAWebView") {
                return false;
            }
            /*if (this.isVisible()) {
             return false;
             } else {
             return true;
             }*/
            var parent = this.parent();
            /*while (parent.parent() && parent.parent().type() !== "UIAElementNil") {
             parent = parent.parent();
             log("parent  " + parent.type() + ", " + parent.name());

             }*/
            return false;
        } catch (err) {
            if (err.message && err.message.indexOf('scrollToVisible cannot be used') != -1) {
                log("F");
                return true;
            } else {
                log("NI");
                log(err)
            }
        }
    }
    log("G");
    return false;
}

UIAElement.prototype.element_or = function (depth, criteria) {
    var all = this.getChildren(depth);
    var res = new Array();

    var keys = getKeys(criteria);

    var possibleResult;
    var resultArea = 10000 * 10000;
    for (var i = 0; i < all.length; i++) {
        var element = all[i];
        if (element.matches(criteria)) {
            if (keys.length === 2) {// if location criteria, need to find the
                // smallest element containing the location
                var elementArea = element.rect().size.width * element.rect().size.height;
                // the = is critical. the 'all' collection has the elements
                // listed in order. For the given tree:
                // parent
                // - child1
                // - child2
                // - child3
                // all will be parent , child1, child2 , child3.
                // if child1 and child2 have the same area, the assumption is
                // that child1 is a container, while child2 is the element
                // the user will interact with.
                // <= instead of < garantees to return child2 and not child1
                if (elementArea <= resultArea) {
                    possibleResult = element;
                    resultArea = elementArea;
                }
            } else {
                return element;
            }

        }
    }
    if (possibleResult) {
        return possibleResult;
    }
    throw new UIAutomationException("cannot find element for criteria :" + JSON.stringify(criteria),
                                    7);
}

UIAElement.prototype.element = function (depth, criteria, opt_timeout_sec) {
    if (UIAutomation.getTimeout('implicit') == 0) {
        return this._element(depth, criteria);
    } else {
        var timeout = UIAutomation.getTimeout('implicit');
        if (!(typeof opt_timeout_sec === "undefined")) {
            timeout = opt_timeout_sec;
        }
        var limit = new Date().getTime() + (1000 * timeout);

        while (new Date().getTime() < limit) {
            try {
                var res = this._element(depth, criteria);
                return res;
            } catch (err) {
                // nothing found.
                UIATarget.localTarget().delay(1);
            }
        }
        throw new UIAutomationException("Timeout after " + timeout
                                            + " seconds.Cannot find element for criteria :" + JSON
                                            .stringify(criteria), 7);

    }

}

UIAElement.prototype._element = function (depth, criteria) {
    // if a criteria contains a OR, first run the search on the whole tree on
    // each criteria in the OR so that A or B returns A, and B or A returns B.
    var keys = getKeys(criteria);

    if (keys.length == 1 && keys[0] === "OR") {
        var array = criteria[keys[0]];
        for (var c in array) {
            var orCriteria = array[c];
            try {
                return this.element(depth, orCriteria);
            } catch (err) {
                // nothing found
            }
        }
        throw new UIAutomationException("cannot find element for criteria :" + JSON
            .stringify(criteria), 7);
    } else {
        return this.element_or(depth, criteria);
    }
}

UIAElement.prototype.elements2 = function (depth, criteria) {
    var all = this.getChildren(depth);
    var res = new Array();
    if (criteria) {
        for (var i = 0; i < all.length; i++) {
            var element = all[i];
            if (element.matches(criteria)) {
                res.push(element);
            }
        }
        return res;
    } else {
        return all;
    }
}

/**
 * Return the ios-driver object associated with the element. Contains all the info used to debug, and
 * accessible by calling the logElementTree command.
 * @return {Object} an ios-driver node representing the UIAElement.
 */
UIAElement.prototype.asNode = function () {
    try {
        return {
            "type": this.type(),
            "name": this.name(),
            "value": this.value(),
            "label": this.label(),
            "rect": this.rect(),
            "ref": this.reference()
        };
    } catch (err) {
        return "error : " + err;
    }
}

/**
 * returns a dump of the application object tree, optionally taking a screenshot.
 * @param {boolean} attachScreenshot  if true, a screenshot will be taken.
 * @return {object}
 */
UIAElement.prototype.tree = function (attachScreenshot) {
    var buildNode = function (element) {
        var res = element.asNode();
        var children = element.elements();
        if (children.length > 0) {
            var array = new Array();
            for (var i = 0; i < children.length; i++) {
                array.push(buildNode(children[i]));
            }
            res.children = array;
        }
        return res;
    }
    var result = {};
    var res = buildNode(this);

    if (attachScreenshot) {
        UIATarget.localTarget().captureScreenWithName('tmpScreenshot');
        result.screenshot = true;
    } else {
        result.screenshot = false;
    }

    result.tree = res;
    // TODO freynaud why is that broken ?
    // result.deviceOrientation = UIATarget.localTarget().deviceOrientation();
    result.deviceOrientation = UIATarget.localTarget().frontMostApp().interfaceOrientation();

    return result;
}
/**
 * return an array with all the children of the element. Goes depth deep in the
 * tree. depth = -1 or undefined gets all elements
 * @param {nulber} depth -1 to return everything.
 * @return {Array} of all the children of that element, and their own children.
 */
UIAElement.prototype.getChildren = function (depth) {
    var res = new Array();

    if (!depth) {
        depth = -1;
    }
    var getChildren = function (depth, element) {
        res.push(element);
        var d = depth - 1;
        if (d > 0 || d !== -1) {
            var children = element.elements();
            var size = children.length;
            for (var i = 0; i < size; i++) {
                var child = children[i];
                getChildren(d, child);
            }
        }
    }
    getChildren(depth, this)
    return res;
}
var getKeys = function (obj) {
    var keys = [];
    for (var key in obj) {
        keys.push(key);
    }
    return keys;
}
/**
 * returns true if the element contains the x,y point specified.
 * @param {number} x
 * @param {number} y
 * @return {boolean} true if (x,y) is in the element.
 */
UIAElement.prototype.contains = function (x, y) {
    var rect = this.rect();

    if (x < Math.floor(rect.origin.x)) {
        return false;
    }
    if (y < Math.floor(rect.origin.y)) {
        return false;
    }
    if (x > (Math.floor(rect.origin.x) + Math.floor(rect.size.width))) {
        return false;
    }
    if (y > (Math.floor(rect.origin.y) + Math.floor(rect.size.height))) {
        return false;
    }
    log("match " + x + "," + y + " is in " + JSON.stringify(this.rect()) + "," + this.name());
    return true;
}

/**
 * returns true if the element matches the given criteria.
 * @param {criteria} criteria the criteria object.
 * @return {boolean} true is the element matches the given criteria.
 */
UIAElement.prototype.matches = function (criteria) {
    if (!criteria) {
        return true;
    }
    // criteria only have one field.It's either :
    // AND + array of criterias
    // OR + array of criterias
    // NOT + criteria
    // criteria
    var keys = getKeys(criteria);

    // empty filder matches all.
    if (keys == 0) {
        return true;
    }

    // AND, OR , NOT + array of criteria to apply
    if (keys.length == 1) {
        var key = keys[0];
        if (key === "AND") {
            var array = criteria[key];
            for (var c in array) {
                if (!this.matches(array[c])) {
                    return false;
                }
            }
            return true;
        } else if (key === "OR") {
            var array = criteria[key];
            for (var c in array) {
                if (this.matches(array[c])) {
                    return true;
                }
            }
            return false;
        } else if (key === "NOT") {
            return !this.matches(criteria[key][0]);
        } else {
            throw new UIAutomationException("not a valid criteria, -> " + JSON.stringify(criteria),
                                            32);
        }

    } else if (keys.length == 2) {// location match

        var x = criteria['x'];
        var y = criteria['y'];
        if (this.contains(x, y)) {
            return true;
            // !this.isStale();
        }
    } else if (keys.length == 4) {// property match
        var method = criteria['method'];
        var expected = criteria['expected'];
        var strategy = criteria['matching'];
        var current = this[method]();

        switch (strategy) {
            case 'exact':
                return current == expected;
            case 'regex':
                var regex = new RegExp(expected, 'g');
                return regex.test(current);
                break;
            default:
                throw new UIAutomationException("not a valid strategy, -> " + JSON
                    .stringify(criteria), 32);
        }

    } else {
        throw new UIAutomationException("not a valid criteria, -> " + JSON.stringify(criteria), 32);
    }
}

UIAElementNil.prototype.type = function () {
    return "UIAElementNil";
}

// TODO freynaud check why this is necessary. key extends elements.
UIAKey.prototype.type = UIAElement.prototype.type;
UIAElementArray.prototype.type = UIAElement.prototype.type;

UIAElementNil.prototype.reference = UIAElement.prototype.reference;

UIAKey.prototype.reference = UIAElement.prototype.reference;

UIAKey.prototype.scrollToVisible = function () {
};

UIAKeyboard.prototype.scrollToVisible = function () {
};




© 2015 - 2024 Weber Informatics LLC | Privacy Policy