Merge pull request #2309 from jlipps/jlipps-1.0-beta

remove old xpath support and promote -real xpath to xpath
This commit is contained in:
Jonathan Lipps
2014-04-09 17:32:53 -07:00
19 changed files with 158 additions and 604 deletions

View File

@@ -46,15 +46,7 @@ androidController.findUIElementOrElements = function (strategy, selector, many,
if (!deviceCommon.checkValidLocStrat(strategy, false, cb)) {
return;
}
if (strategy === "xpath") {
warnDeprecatedCustom('locator strategy', 'xpath',
"You used the xpath locator strategy to find an element. Xpath " +
"support is undergoing massive change, which will become active " +
"in Appium 1.0. To try out the new version of xpath, use the " +
"'-real xpath' locator strategy instead of 'xpath'. Please take the " +
"time to update your tests soon.");
}
if (strategy === "-real xpath" && context) {
if (strategy === "xpath" && context) {
return cb(new Error("Cannot use xpath locator strategy from an element. " +
"It can only be used from the root element"));
}
@@ -64,27 +56,11 @@ androidController.findUIElementOrElements = function (strategy, selector, many,
, context: context
, multiple: many
};
var xpathError = false;
if (strategy === "xpath") {
var xpathParams = parseXpath(selector);
if (!xpathParams) {
xpathError = true;
} else {
// massage for the javas
if (xpathParams.attr === null) {
xpathParams.attr = "";
}
if (xpathParams.constraint === null) {
xpathParams.constraint = "";
}
params = _.extend(params, xpathParams);
}
}
if (strategy === "name") {
helpers.logDeprecationWarning("Locator strategy", '"name"', '"accessibility id"');
}
var doFind = function (findCb) {
if (strategy === "-real xpath") {
if (strategy === "xpath") {
this.findUIElementsByXPath(selector, many, function (err, res) {
this.handleFindCb(err, res, many, findCb);
}.bind(this));
@@ -94,14 +70,7 @@ androidController.findUIElementOrElements = function (strategy, selector, many,
}.bind(this));
}
}.bind(this);
if (!xpathError) {
this.waitForCondition(this.implicitWaitMs, doFind, cb);
} else {
cb(null, {
status: status.codes.XPathLookupError.code
, value: "Could not parse xpath data from " + selector
});
}
this.waitForCondition(this.implicitWaitMs, doFind, cb);
};
androidController.handleFindCb = function (err, res, many, findCb) {

View File

@@ -1,23 +1,39 @@
package io.appium.android.bootstrap.handler;
import android.os.Build;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiScrollable;
import com.android.uiautomator.core.UiSelector;
import io.appium.android.bootstrap.*;
import io.appium.android.bootstrap.exceptions.*;
import io.appium.android.bootstrap.AndroidCommand;
import io.appium.android.bootstrap.AndroidCommandResult;
import io.appium.android.bootstrap.AndroidElement;
import io.appium.android.bootstrap.AndroidElementClassMap;
import io.appium.android.bootstrap.AndroidElementsHash;
import io.appium.android.bootstrap.CommandHandler;
import io.appium.android.bootstrap.Dynamic;
import io.appium.android.bootstrap.Logger;
import io.appium.android.bootstrap.WDStatus;
import io.appium.android.bootstrap.exceptions.AndroidCommandException;
import io.appium.android.bootstrap.exceptions.ElementNotFoundException;
import io.appium.android.bootstrap.exceptions.ElementNotInHashException;
import io.appium.android.bootstrap.exceptions.InvalidStrategyException;
import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException;
import io.appium.android.bootstrap.exceptions.UnallowedTagNameException;
import io.appium.android.bootstrap.selector.Strategy;
import io.appium.android.bootstrap.utils.UiSelectorParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.Build;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiScrollable;
import com.android.uiautomator.core.UiSelector;
/**
* This handler is used to find elements in the Android UI.
*
@@ -27,25 +43,10 @@ import java.util.List;
*/
public class Find extends CommandHandler {
// These variables are expected to persist across executions.
AndroidElementsHash elements = AndroidElementsHash.getInstance();
Dynamic dynamic = new Dynamic();
public static JSONObject apkStrings = null;
UiSelectorParser uiSelectorParser = new UiSelectorParser();
private Object[] cascadeChildSels(final ArrayList<UiSelector> tail,
final ArrayList<String> tailOuts) {
if (tail.size() == 1) {
final Object[] retVal = { tail.get(0), tailOuts.get(0) };
return retVal;
} else {
final UiSelector head = tail.remove(0);
final String headOut = tailOuts.remove(0);
final Object[] res = cascadeChildSels(tail, tailOuts);
final Object[] retVal = { head.childSelector((UiSelector) res[0]),
headOut + ".childSelector(" + (String) res[1] + ")" };
return retVal;
}
}
AndroidElementsHash elements = AndroidElementsHash.getInstance();
Dynamic dynamic = new Dynamic();
public static JSONObject apkStrings = null;
UiSelectorParser uiSelectorParser = new UiSelectorParser();
/*
* @param command The {@link AndroidCommand} used for this handler.
@@ -63,8 +64,7 @@ public class Find extends CommandHandler {
final Hashtable<String, Object> params = command.params();
if (((String) params.get("strategy")).equals("index paths")) {
return findElementsByIndexPaths((String) params.get("selector"),
(Boolean) params.get("multiple"));
}
// only makes sense on a device
@@ -74,7 +74,17 @@ public class Find extends CommandHandler {
} catch (final InvalidStrategyException e) {
return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, e.getMessage());
}
final String contextId = (String) params.get("context");
final String text = (String) params.get("selector");
final Boolean multiple = (Boolean) params.get("multiple");
Logger.debug("Finding " + text + " using " + strategy.toString()
+ " with the contextId: " + contextId);
if (strategy == Strategy.INDEX_PATHS) {
return findElementsByIndexPaths(text, multiple);
}
if (strategy == Strategy.DYNAMIC) {
Logger.debug("Finding dynamic.");
@@ -184,95 +194,53 @@ public class Find extends CommandHandler {
}
}
final String text = (String) params.get("selector");
Logger.debug("Finding " + text + " using " + strategy.toString()
+ " with the contextId: " + contextId);
final Boolean multiple = (Boolean) params.get("multiple");
final boolean isXpath = strategy.equalsIgnoreCase("xpath");
if (isXpath) {
final JSONArray xpathPath = (JSONArray) params.get("path");
final String xpathAttr = (String) params.get("attr");
final String xpathConstraint = (String) params.get("constraint");
final Boolean xpathSubstr = (Boolean) params.get("substr");
try {
if (multiple) {
final UiSelector sel = getSelectorForXpath(xpathPath, xpathAttr,
xpathConstraint, xpathSubstr);
return getSuccessResult(fetchElements(sel, contextId));
} else {
final UiSelector sel = getSelectorForXpath(xpathPath, xpathAttr,
xpathConstraint, xpathSubstr);
return getSuccessResult(fetchElement(sel, contextId));
}
} catch (final AndroidCommandException e) {
return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
} catch (final ElementNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
e.getMessage());
} catch (final UnallowedTagNameException e) {
return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
} catch (final ElementNotInHashException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
e.getMessage());
} catch (final UiObjectNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
e.getMessage());
}
} else {
try {
Object result = null;
final JSONArray array = new JSONArray();
for (final UiSelector sel : getSelector(strategy, text, multiple)) {
// With multiple selectors, we expect that some elements may not
// exist.
try {
if (!multiple) {
result = fetchElement(sel, contextId);
// Return first element when multiple is false.
if (result != null) {
break;
}
} else {
final JSONArray results = fetchElements(sel, contextId);
for (int a = 0, len = results.length(); a < len; a++) {
array.put(results.get(a));
}
try {
Object result = null;
final JSONArray array = new JSONArray();
for (final UiSelector sel : getSelector(strategy, text, multiple)) {
// With multiple selectors, we expect that some elements may not
// exist.
try {
if (!multiple) {
result = fetchElement(sel, contextId);
// Return first element when multiple is false.
if (result != null) {
break;
}
} else {
final JSONArray results = fetchElements(sel, contextId);
for (int a = 0, len = results.length(); a < len; a++) {
array.put(results.get(a));
}
} catch (final ElementNotInHashException e) {
} catch (final ElementNotFoundException e) {
}
} catch (final ElementNotInHashException e) {
} catch (final ElementNotFoundException e) {
}
if (multiple) {
result = array;
}
// If there are no results, then return an error.
if (result == null) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
"No element found");
}
return getSuccessResult(result);
} catch (final InvalidStrategyException e) {
return getErrorResult(e.getMessage());
} catch (final UnallowedTagNameException e) {
return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
} catch (final AndroidCommandException e) {
return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
} catch (final UiSelectorSyntaxException e) {
return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, e.getMessage());
} catch (final UiObjectNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
e.getMessage());
} catch (final ElementNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
e.getMessage());
}
if (multiple) {
result = array;
}
// If there are no results, then return an error.
if (result == null) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
"No element found");
}
return getSuccessResult(result);
} catch (final InvalidStrategyException e) {
return getErrorResult(e.getMessage());
} catch (final UnallowedTagNameException e) {
return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
} catch (final AndroidCommandException e) {
return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
} catch (final UiSelectorSyntaxException e) {
return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, e.getMessage());
} catch (final UiObjectNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage());
} catch (final ElementNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage());
}
}
@@ -469,8 +437,9 @@ public class Find extends CommandHandler {
case ANDROID_UIAUTOMATOR:
try {
sel = uiSelectorParser.parse(text);
} catch (UiSelectorSyntaxException e) {
throw new UiSelectorSyntaxException("Could not parse UiSelector argument: " + e.getMessage());
} catch (final UiSelectorSyntaxException e) {
throw new UiSelectorSyntaxException(
"Could not parse UiSelector argument: " + e.getMessage());
}
if (!many) {
sel = sel.instance(0);
@@ -488,119 +457,6 @@ public class Find extends CommandHandler {
return selectors;
}
/**
* Create and return a UiSelector based on Xpath attributes.
*
* @param path
* The Xpath path.
* @param attr
* The attribute.
* @param constraint
* Any constraint.
* @param substr
* Any substr.
*
* @return UiSelector
* @throws AndroidCommandException
*/
private UiSelector getSelectorForXpath(final JSONArray path,
final String attr, final String constraint, final boolean substr)
throws AndroidCommandException, UnallowedTagNameException {
UiSelector s = new UiSelector();
final ArrayList<UiSelector> subSels = new ArrayList<UiSelector>();
final ArrayList<String> subSelOuts = new ArrayList<String>();
JSONObject pathObj;
String nodeType;
Object nodeIndex;
final String substrStr = substr ? "true" : "false";
Logger.info("Building xpath selector from attr " + attr
+ " and constraint " + constraint + " and substr " + substrStr);
String selOut = "s";
// $driver.find_element :xpath, %(//*[contains(@text, 'agree')])
// info: [ANDROID] [info] Building xpath selector from attr text and
// constraint agree and substr true
// info: [ANDROID] [info] s.className('*').textContains('agree')
try {
nodeType = path.getJSONObject(0).getString("node");
} catch (final JSONException e) {
throw new AndroidCommandException(
"Error parsing xpath path obj from JSON");
}
if (attr.toLowerCase().contentEquals("text") && !constraint.isEmpty()
&& substr == true && nodeType.contentEquals("*") == true) {
selOut += ".textContains('" + constraint + "')";
s = s.textContains(constraint);
Logger.info(selOut);
return s;
}
// Returns all elements of one class.
// //*[contains(@tag, "android.widget.Button")]
if (attr.toLowerCase().contentEquals("tag") && !constraint.isEmpty()
&& substr == true && nodeType.contentEquals("*") == true) {
selOut += ".className('" + constraint + "')";
s = s.className(constraint);
Logger.info(selOut);
return s;
}
for (Integer i = 0; i < path.length(); i++) {
UiSelector subSel = new UiSelector();
String subSelOut = "s" + i.toString();
try {
pathObj = path.getJSONObject(i);
nodeType = pathObj.getString("node");
nodeIndex = pathObj.get("index");
} catch (final JSONException e) {
throw new AndroidCommandException(
"Error parsing xpath path obj from JSON");
}
nodeType = AndroidElementClassMap.match(nodeType);
subSel = subSel.className(nodeType);
subSelOut += ".className('" + nodeType + "')";
try {
Integer nodeIndexInt = (Integer) nodeIndex;
if (nodeIndexInt == -1) {
nodeIndexInt = elements.getElements(subSel, "").size();
}
nodeIndexInt -= 1;
subSel = subSel.instance(nodeIndexInt);
subSelOut += ".instance(" + nodeIndexInt.toString() + ")";
} catch (final Exception e) {
// nodeIndex was null
}
subSels.add(subSel);
subSelOuts.add(subSelOut);
}
final Object[] cascadeResult = cascadeChildSels(subSels, subSelOuts);
s = (UiSelector) cascadeResult[0];
selOut = (String) cascadeResult[1];
if (attr.equals("desc") || attr.equals("name")) {
selOut += ".description";
if (substr) {
selOut += "Contains";
s = s.descriptionContains(constraint);
} else {
s = s.description(constraint);
}
selOut += "('" + constraint + "')";
} else if (attr.equals("text") || attr.equals("value")) {
selOut += ".text";
if (substr) {
selOut += "Contains";
s = s.textContains(constraint);
} else {
s = s.text(constraint);
}
selOut += "('" + constraint + "')";
}
Logger.info(selOut);
return s;
}
private UiSelector selectNameOrText(final boolean many, final String text) {
UiSelector sel = new UiSelector();
sel = sel.description(text);

View File

@@ -4,15 +4,17 @@ import io.appium.android.bootstrap.exceptions.InvalidStrategyException;
/**
* An emumeration of possible strategies.
*
*
*/
public enum Strategy {
CLASS_NAME(0, "class name"), CSS_SELECTOR(1, "css selector"), ID(2, "id"), NAME(
3, "name"), LINK_TEXT(4, "link text"), PARTIAL_LINK_TEXT(5,
"partial link text"), TAG_NAME(6, "tag name"), XPATH(7, "xpath"), DYNAMIC(
8, "dynamic"), ACCESSIBILITY_ID(9, "accessibility id"), ANDROID_UIAUTOMATOR(10, "-android uiautomator");
"partial link text"), TAG_NAME(6, "tag name"), INDEX_PATHS(7,
"index paths"), DYNAMIC(8, "dynamic"), ACCESSIBILITY_ID(9,
"accessibility id"), ANDROID_UIAUTOMATOR(10, "-android uiautomator");
public static Strategy fromString(final String text) throws InvalidStrategyException {
public static Strategy fromString(final String text)
throws InvalidStrategyException {
if (text != null) {
for (final Strategy s : Strategy.values()) {
if (text.equalsIgnoreCase(s.strategyName)) {
@@ -20,8 +22,8 @@ public enum Strategy {
}
}
}
throw new InvalidStrategyException("Locator strategy '" + text +
"' is not supported on Android");
throw new InvalidStrategyException("Locator strategy '" + text
+ "' is not supported on Android");
}
private final int strategyCode;

View File

@@ -49,9 +49,6 @@ iOSController.createGetElementCommand = function (strategy, selector, ctx, many)
case "accessibility id":
command = ["au.getElement", ext, "ByName('", selector, "'", ctx, ")"].join('');
break;
case "xpath":
command = ["au.getElement", ext, "ByXpath('", selector, "'", ctx, ")"].join('');
break;
case "id":
selector = selector.replace(/'/g, "\\\\'"); // must escape single quotes
command = ["var exact = au.mainApp().getFirstWithPredicateWeighted(\"name == '", selector,
@@ -71,15 +68,7 @@ iOSController.createGetElementCommand = function (strategy, selector, ctx, many)
};
iOSController.findUIElementOrElements = function (strategy, selector, ctx, many, cb) {
if (strategy === "xpath") {
warnDeprecatedCustom('locator strategy', 'xpath',
"You used the xpath locator strategy to find an element. Xpath " +
"support is undergoing massive change, which will become active " +
"in Appium 1.0. To try out the new version of xpath, use the " +
"'-real xpath' locator strategy instead of 'xpath'. Please take the " +
"time to update your tests soon.");
}
if (strategy !== "-real xpath") {
if (strategy !== "xpath") {
selector = escapeSpecialChars(selector, "'");
}
if (typeof ctx === "undefined" || !ctx) {
@@ -100,7 +89,7 @@ iOSController.findUIElementOrElements = function (strategy, selector, ctx, many,
if (!selector) return;
var doFind = function (findCb) {
if (strategy === "-real xpath") {
if (strategy === "xpath") {
this.findUIElementsByXpath(selector, ctx, many, function (err, res) {
this.handleFindCb(err, res, many, findCb);
}.bind(this));
@@ -310,7 +299,7 @@ iOSController.findElementsFromElement = function (element, strategy, selector, c
};
iOSController.findAndAct = function (strategy, selector, index, action, actionParams, cb) {
var stratMap = {'name': 'Name', 'xpath': 'Xpath', 'tag name': 'Type'}
var stratMap = {'name': 'Name', 'class name': 'Type'}
// if you change these, also change in
// app/uiauto/appium/app.js:elemForAction
, supportedActions = ["tap", "isEnabled", "isValid", "isVisible",

View File

@@ -82,7 +82,7 @@ describe("apidemo - find elements -", function () {
it('should find a single element by id', function (done) {
driver
.execute("mobile: find", [["scroll", [[3, "views"]], [[7, "views"]]]]).click()
.elementByXPath("//text[@text='Buttons']").click()
.elementByXPath("//android.widget.TextView[@text='Buttons']").click()
.elementById("buttons_1_normal").text().should.become("Normal")
.nodeify(done);
});
@@ -128,84 +128,44 @@ describe("apidemo - find elements -", function () {
});
describe('xpath', function () {
it('should find element by type', function (done) {
driver
.elementByXPath('//text').text()
.should.become("API Demos")
.nodeify(done);
});
it('should find element by text', function (done) {
driver
.elementByXPath("//text[@value='Accessibility']").text()
.should.become("Accessibility")
.nodeify(done);
});
it('should find element by partial text', function (done) {
driver
.elementByXPath("//text[contains(@value, 'Accessibility')]").text()
.should.become("Accessibility")
.nodeify(done);
});
it('should find the last element', function (done) {
driver
.elementByXPath("//text[last()]").text()
.then(function (text) {
["OS", "Text", "Views"].should.include(text);
}).nodeify(done);
});
it('should find element by xpath index and child', function (done) {
driver
.elementByXPath("//frame[1]/frame[1]/list[1]/text[3]").text()
.should.become("App")
.nodeify(done);
});
it('should find element by index and embedded desc', function (done) {
driver
.elementByXPath("//frame/frame[1]//text[3]").text()
.should.become("App")
.nodeify(done);
});
});
describe('real xpath', function () {
var f = "android.widget.FrameLayout";
var l = "android.widget.ListView";
var t = "android.widget.TextView";
var v = "android.view.View";
it('should find element by type', function (done) {
driver
.elementByRealXPath('//' + t).text()
.elementByXPath('//' + t).text()
.should.become("API Demos")
.nodeify(done);
});
it('should find element by text', function (done) {
driver
.elementByRealXPath("//" + t + "[@text='Accessibility']").text()
.elementByXPath("//" + t + "[@text='Accessibility']").text()
.should.become("Accessibility")
.nodeify(done);
});
it('should find element by partial text', function (done) {
driver
.elementByRealXPath("//" + t + "[contains(@text, 'Accessibility')]").text()
.elementByXPath("//" + t + "[contains(@text, 'Accessibility')]").text()
.should.become("Accessibility")
.nodeify(done);
});
it('should find the last element', function (done) {
driver
.elementByRealXPath("//" + t + "[last()]").text()
.elementByXPath("//" + t + "[last()]").text()
.then(function (text) {
["OS", "Text", "Views"].should.include(text);
}).nodeify(done);
});
it('should find element by xpath index and child', function (done) {
driver
.elementByRealXPath("//" + f + "[1]/" + v + "[1]/" + f + "[2]/" + l + "[1]/" + t + "[3]").text()
.elementByXPath("//" + f + "[1]/" + v + "[1]/" + f + "[2]/" + l + "[1]/" + t + "[3]").text()
.should.become("App")
.nodeify(done);
});
it('should find element by index and embedded desc', function (done) {
driver
.elementByRealXPath("//" + f + "//" + t + "[4]").text()
.elementByXPath("//" + f + "//" + t + "[4]").text()
.should.become("App")
.nodeify(done);
});

View File

@@ -4,6 +4,7 @@ var env = require('../../../helpers/env')
, setup = require("../../common/setup-base")
, desired = require("./desired")
, androidReset = require('../../../helpers/reset').androidReset
, droidText = 'android.widget.TextView'
, Q = require("q");
describe("apidemo - gestures -", function () {
@@ -104,11 +105,11 @@ describe("apidemo - gestures -", function () {
.then(function (el) {
scrollOpts = { element: el.value, text: 'Views' };
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Views']").click()
}).elementByXPath("//" + droidText + "[@value='Views']").click()
.then(function () {
scrollOpts.text = 'Drag and Drop';
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Drag and Drop']").click()
}).elementByXPath("//" + droidText + "[@value='Drag and Drop']").click()
.then(function () {
return Q.all([
driver.elementById("com.example.android.apis:id/drag_dot_3").getLocation(),
@@ -137,11 +138,11 @@ describe("apidemo - gestures -", function () {
, text: 'Views'
};
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Views']").click()
}).elementByXPath("//" + droidText + "[@value='Views']").click()
.then(function () {
scrollOpts.text = 'Drag and Drop';
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Drag and Drop']").click()
}).elementByXPath("//" + droidText + "[@value='Drag and Drop']").click()
.then(function () {
return Q.all([
driver.elementById("com.example.android.apis:id/drag_dot_3"),
@@ -169,11 +170,11 @@ describe("apidemo - gestures -", function () {
, text: 'Views'
};
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Views']").click()
}).elementByXPath("//" + droidText + "[@value='Views']").click()
.then(function () {
scrollOpts.text = 'Drag and Drop';
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Drag and Drop']").click()
}).elementByXPath("//" + droidText + "[@value='Drag and Drop']").click()
.then(function () {
return Q.all([
driver.elementById("com.example.android.apis:id/drag_dot_3"),
@@ -214,11 +215,11 @@ describe("apidemo - gestures -", function () {
, text: 'Views'
};
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Views']").click()
}).elementByXPath("//" + droidText + "[@value='Views']").click()
.then(function () {
scrollOpts.text = 'WebView';
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='WebView']").click()
}).elementByXPath("//" + droidText + "[@value='WebView']").click()
.elementById("com.example.android.apis:id/wv1")
.then(function (el) {
var pinchOpts = {

View File

@@ -15,7 +15,7 @@ describe("apidemo - location -", function () {
setup(this, desired).then(function (d) { driver = d; });
it('should set geo location', function (done) {
var getText = function () { return driver.elementByXPath("//text[2]").text(); };
var getText = function () { return driver.elementByXPath("//android.widget.TextView[2]").text(); };
var newLat = "27.17";
var newLong = "78.04";
driver

View File

@@ -7,6 +7,7 @@ var env = require('../../../helpers/env')
, Q = require("q")
, _ = require("underscore")
, wd = require("wd")
, droidText = 'android.widget.TextView'
, TouchAction = wd.TouchAction
, MultiAction = wd.MultiAction;
@@ -301,14 +302,14 @@ describe("apidemo - gestures -", function () {
, text: 'Views'
};
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Views']")
}).elementByXPath("//" + droidText + "[@text='Views']")
.then(function (el) {
return new TouchAction().tap().performOn(el);
})
.then(function () {
scrollOpts.text = 'Splitting Touches across Views';
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Splitting Touches across Views']")
}).elementByXPath("//" + droidText + "[@text='Splitting Touches across Views']")
.then(function (el) {
return new TouchAction().tap().performOn(el);
})
@@ -350,14 +351,14 @@ describe("apidemo - gestures -", function () {
, text: 'Views'
};
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Views']")
}).elementByXPath("//" + droidText + "[@text='Views']")
.then(function (el) {
return new TouchAction().tap().performOn(el);
})
.then(function () {
scrollOpts.text = 'Splitting Touches across Views';
return driver.execute("mobile: scrollTo", [scrollOpts]);
}).elementByXPath("//text[@value='Splitting Touches across Views']")
}).elementByXPath("//" + droidText + "[@text='Splitting Touches across Views']")
.then(function (el) {
return new TouchAction().tap().performOn(el);
})

View File

@@ -10,14 +10,6 @@ chai.should();
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
require("colors");
wd.addPromiseChainMethod('elementByRealXPath', function (selector) {
return this.element('-real xpath', selector);
});
wd.addPromiseChainMethod('elementsByRealXPath', function (selector) {
return this.elements('-real xpath', selector);
});
module.exports = function (context, desired, opts) {
context.timeout(env.MOCHA_INIT_TIMEOUT);

View File

@@ -26,7 +26,7 @@ describe("prefs @skip-ios7", function () {
.execute("mobile: findAndAct", [clickGeneral])
.sleep(1000)
.execute("mobile: findAndAct", [clickKeyboard])
.elementByXPath('//switch[@name="Auto-Correction"]')
.elementByXPath('//UIASwitch[@name="Auto-Correction"]')
.then(function (el) { switchEl = el; return el; })
.getValue().then(function (checked) {
if (checked === 1) return switchEl.click();

View File

@@ -16,10 +16,10 @@ describe('testapp - pinch gesture -', function () {
driver
.elementsByTagName('button').at(5).click()
.sleep(1000).then(function () { okIfAlert(driver); })
.elementByXPath('//window[1]/UIAMapView[1]')
.elementByXPath('//UIAWindow[1]/UIAMapView[1]')
.execute("mobile: pinchOpen", [{startX: 114.0, startY: 198.0, endX: 257.0,
endY: 256.0, duration: 5.0}])
.elementByXPath('//window[1]/UIAMapView[1]')
.elementByXPath('//UIAWindow[1]/UIAMapView[1]')
.execute("mobile: pinchClose", [{startX: 114.0, startY: 198.0, endX: 257.0,
endY: 256.0, duration: 5.0}])
.nodeify(done);
@@ -58,7 +58,7 @@ describe('testapp - touch actions @skip-ios-all -', function () {
.performTouch(tap)
.sleep(500).then(function () { okIfAlert(driver); })
.sleep(500)
.elementByXPath('//window[1]/UIAMapView[1]')
.elementByXPath('//UIAWindow[1]/UIAMapView[1]')
.performTouch((new TouchAction()).press().moveTo({ x: 0, y: 100 }).release())
.sleep(15000)
.nodeify(done);
@@ -68,7 +68,7 @@ describe('testapp - touch actions @skip-ios-all -', function () {
describe('wait', function () {
it('should move the page and wait a bit', function (done) {
driver
.elementByXPath('//window[1]/UIAMapView[1]')
.elementByXPath('//UIAWindow[1]/UIAMapView[1]')
.performTouch(new TouchAction().press().moveTo({ x: 0, y: 100 })
.wait({ ms: 5000 }).moveTo({ x: 0, y: -100 }).release())
.sleep(15000)
@@ -84,7 +84,7 @@ describe('testapp - touch actions @skip-ios-all -', function () {
);
driver
.sleep(500)
.elementByXPath('//window[1]/UIAMapView[1]')
.elementByXPath('//UIAWindow[1]/UIAMapView[1]')
.performMultiTouch(multiAction)
.sleep(15000)
.nodeify(done);
@@ -97,7 +97,7 @@ describe('testapp - touch actions @skip-ios-all -', function () {
);
driver
.sleep(500)
.elementByXPath('//window[1]/UIAMapView[1]')
.elementByXPath('//UIAWindow[1]/UIAMapView[1]')
.performMultiTouch(multiAction)
.sleep(15000)
.nodeify(done);

View File

@@ -32,9 +32,9 @@ describe('uicatalog - alerts -', function () {
it('should detect Show Simple', function (done) {
driver
.elementByXPath("//text[contains(@label,'Alerts')]").click()
.waitForElementByXPath("//text[contains(" + alertTag + ",'Show Simple')]", 10000, 1000)
.elementsByXPath("//text[contains(" + alertTag + ",'Show Simple')]")
.elementByXPath("//UIAStaticText[contains(@label,'Alerts')]").click()
.waitForElementByXPath("//UIAStaticText[contains(" + alertTag + ",'Show Simple')]", 10000, 1000)
.elementsByXPath("//UIAStaticText[contains(" + alertTag + ",'Show Simple')]")
.at(1).click()
.resolve(waitForAlert())
.nodeify(done);
@@ -46,9 +46,9 @@ describe('uicatalog - alerts -', function () {
it('should detect Show OK-Cancel', function (done) {
driver
.elementByXPath("//text[contains(@label,'Alerts')]").click()
.waitForElementByXPath("//text[contains(" + alertTag + ",'Show OK-Cancel')]", 10000, 1000)
.elementsByXPath("//text[contains(" + alertTag + ",'Show OK-Cancel')]")
.elementByXPath("//UIAStaticText[contains(@label,'Alerts')]").click()
.waitForElementByXPath("//UIAStaticText[contains(" + alertTag + ",'Show OK-Cancel')]", 10000, 1000)
.elementsByXPath("//UIAStaticText[contains(" + alertTag + ",'Show OK-Cancel')]")
.at(1).click()
.resolve(waitForAlert())
.nodeify(done);
@@ -60,9 +60,9 @@ describe('uicatalog - alerts -', function () {
it('should detect Show Custom', function (done) {
driver
.elementByXPath("//text[contains(@label,'Alerts')]").click()
.waitForElementByXPath("//text[contains(" + alertTag + ",'Show Custom')]", 10000, 1000)
.elementsByXPath("//text[contains(" + alertTag + ",'Show Custom')]")
.elementByXPath("//UIAStaticText[contains(@label,'Alerts')]").click()
.waitForElementByXPath("//UIAStaticText[contains(" + alertTag + ",'Show Custom')]", 10000, 1000)
.elementsByXPath("//UIAStaticText[contains(" + alertTag + ",'Show Custom')]")
.at(1).click()
.resolve(waitForAlert())
.nodeify(done);

View File

@@ -38,16 +38,16 @@ describe('uicatalog - basic -', function () {
it('should confirm element is selected @skip-ios7', function (done) {
driver
.elementByXPath("text[contains(@text, 'Picker')]").click()
.elementByXPath("button[contains(@text, 'UIPicker')]").isSelected()
.elementByXPath("//UIAStaticText[contains(@label, 'Pickers')]").click()
.elementByXPath("//UIAButton[contains(@label, 'UIPicker')]").isSelected()
.should.eventually.be.ok
.nodeify(done);
});
it('should confirm element is not selected returns false', function (done) {
driver
.elementByXPath("text[contains(@text, 'Picker')]").click()
.elementByXPath("button[contains(@text, 'Custom')]").isSelected()
.elementByXPath("//UIAStaticText[contains(@label, 'Pickers')]").click()
.elementByXPath("//UIAButton[contains(@label, 'Custom')]").isSelected()
.should.not.eventually.be.ok
.nodeify(done);
});

View File

@@ -16,11 +16,11 @@ describe('uicatalog - controls -', function () {
.nodeify(done);
});
}
it('should be able to get and set a picker value', function (done) {
var picketIdx = env.IOS7 ? 0 : 2; // TODO: why?
driver
.elementByXPath("//text[contains(@label,'Pickers')]").click()
.elementByXPath("//UIAStaticText[contains(@label,'Pickers')]").click()
.elementsByTagName("picker").at(picketIdx)
.elementByTagName('>', "pickerwheel")
.then(function (wheel) {
@@ -38,7 +38,7 @@ describe('uicatalog - controls -', function () {
it('should be able to get and set a slider value', function (done) {
driver
.elementByXPath("//text[contains(@label,'Controls')]").click()
.elementByXPath("//UIAStaticText[contains(@label,'Controls')]").click()
.elementByTagName("slider").then(function (slider) {
return slider
.getAttribute("value").should.become('50%')

View File

@@ -16,8 +16,8 @@ describe('uicatalog - find and act -', function () {
.nodeify(done);
});
}
_.each({'tag name': 'cell', xpath: '//cell'}, function (sel, strat) {
_.each({'class name': 'UIATableCell'}, function (sel, strat) {
it('should tap immediately on an element by ' + strat, function (done) {
var opts = {strategy: strat, selector: sel};
driver

View File

@@ -149,7 +149,6 @@ describe('uicatalog - find element -', function () {
});
});
describe('findElement(s)ByXPath', function () {
var setupXpath = function (driver) {
return driver.elementByTagName('tableCell').click();
@@ -166,35 +165,35 @@ describe('uicatalog - find element -', function () {
it('should return the last button', function (done) {
driver
.resolve(setupXpath(driver))
.elementByXPath("//button[last()]").text()
.elementByXPath("//UIAButton[last()]").text()
.should.become("Add contact")
.nodeify(done);
});
it('should return a single element', function (done) {
driver
.resolve(setupXpath(driver))
.elementByXPath("//button").text()
.elementByXPath("//UIAButton").text()
.should.become("Back")
.nodeify(done);
});
it('should return multiple elements', function (done) {
driver
.resolve(setupXpath(driver))
.elementsByXPath("//button")
.elementsByXPath("//UIAButton")
.should.eventually.have.length.above(5)
.nodeify(done);
});
it('should filter by name', function (done) {
driver
.resolve(setupXpath(driver))
.elementByXPath("button[@name='Rounded']").text()
.elementByXPath("//UIAButton[@name='Rounded']").text()
.should.become("Rounded")
.nodeify(done);
});
it('should know how to restrict root-level elements', function (done) {
driver
.resolve(setupXpath(driver))
.elementByXPath("/button")
.elementByXPath("/UIAButton")
.should.be.rejectedWith(/status: 7/)
.nodeify(done);
});
@@ -203,7 +202,7 @@ describe('uicatalog - find element -', function () {
.resolve(setupXpath(driver))
.then(function () {
return spinWait(function () {
return driver.elementByXPath("navigationBar/text")
return driver.elementByXPath("//UIANavigationBar/UIAStaticText")
.text().should.become('Buttons');
});
}).nodeify(done);
@@ -211,7 +210,7 @@ describe('uicatalog - find element -', function () {
it('should search an extended path by descendant', function (done) {
driver
.resolve(setupXpath(driver))
.elementsByXPath("cell//button").then(function (els) {
.elementsByXPath("//UIATableCell//UIAButton").then(function (els) {
return Q.all(_(els).map(function (el) { return el.text(); }));
}).then(function (texts) {
texts.should.not.include("Button");
@@ -223,7 +222,7 @@ describe('uicatalog - find element -', function () {
.resolve(setupXpath(driver))
.then(function () {
return spinWait(function () {
return driver.elementByXPath("cell[2]//text[1]").getAttribute('name')
return driver.elementByXPath("//UIATableCell[2]//UIAStaticText[1]").getAttribute('name')
.should.become("ButtonsViewController.m:\r(UIButton *)grayButton");
});
}).nodeify(done);
@@ -231,94 +230,7 @@ describe('uicatalog - find element -', function () {
it('should filter by partial text', function (done) {
driver
.resolve(setupXpath(driver))
.elementByXPath("cell//button[contains(@name, 'Gr')]").text()
.should.become("Gray")
.nodeify(done);
});
});
describe('findElement(s)ByRealXPath', function () {
var setupXpath = function (driver) {
return driver.elementByTagName('tableCell').click();
};
if (process.env.FAST_TESTS) {
afterEach(function (done) {
driver
.back()
.nodeify(done);
});
}
it('should return the last button', function (done) {
driver
.resolve(setupXpath(driver))
.elementByRealXPath("//UIAButton[last()]").text()
.should.become("Add contact")
.nodeify(done);
});
it('should return a single element', function (done) {
driver
.resolve(setupXpath(driver))
.elementByRealXPath("//UIAButton").text()
.should.become("Back")
.nodeify(done);
});
it('should return multiple elements', function (done) {
driver
.resolve(setupXpath(driver))
.elementsByRealXPath("//UIAButton")
.should.eventually.have.length.above(5)
.nodeify(done);
});
it('should filter by name', function (done) {
driver
.resolve(setupXpath(driver))
.elementByRealXPath("//UIAButton[@name='Rounded']").text()
.should.become("Rounded")
.nodeify(done);
});
it('should know how to restrict root-level elements', function (done) {
driver
.resolve(setupXpath(driver))
.elementByRealXPath("/UIAButton")
.should.be.rejectedWith(/status: 7/)
.nodeify(done);
});
it('should search an extended path by child', function (done) {
driver
.resolve(setupXpath(driver))
.then(function () {
return spinWait(function () {
return driver.elementByRealXPath("//UIANavigationBar/UIAStaticText")
.text().should.become('Buttons');
});
}).nodeify(done);
});
it('should search an extended path by descendant', function (done) {
driver
.resolve(setupXpath(driver))
.elementsByRealXPath("//UIATableCell//UIAButton").then(function (els) {
return Q.all(_(els).map(function (el) { return el.text(); }));
}).then(function (texts) {
texts.should.not.include("Button");
texts.should.include("Gray");
}).nodeify(done);
});
it('should filter by indices', function (done) {
driver
.resolve(setupXpath(driver))
.then(function () {
return spinWait(function () {
return driver.elementByRealXPath("//UIATableCell[2]//UIAStaticText[1]").getAttribute('name')
.should.become("ButtonsViewController.m:\r(UIButton *)grayButton");
});
}).nodeify(done);
});
it('should filter by partial text', function (done) {
driver
.resolve(setupXpath(driver))
.elementByRealXPath("//UIATableCell//UIAButton[contains(@name, 'Gr')]").text()
.elementByXPath("//UIATableCell//UIAButton[contains(@name, 'Gr')]").text()
.should.become("Gray")
.nodeify(done);
});

View File

@@ -12,7 +12,7 @@ describe('uicatalog - move -', function () {
it('should be able to click on arbitrary x-y elements', function (done) {
driver
.elementByTagName('tableCell').moveTo(10, 10).click()
.elementByXPath("button[@name='Rounded']")
.elementByXPath("//UIAButton[@name='Rounded']")
.should.eventually.exist
.nodeify(done);
});

View File

@@ -22,10 +22,6 @@ describe('ios-controller', function () {
var actual = createGetElementCommand('name', 'UIAKey', null, false);
actual.should.equal("au.getElementByName('UIAKey')");
});
it('should return \'GetType\' for xpath selection', function () {
var actual = createGetElementCommand('xpath', 'UIAKey', null, false);
actual.should.equal("au.getElementByXpath('UIAKey')");
});
it('should return \'GetType\' for id selection', function () {
var actual = createGetElementCommand('id', 'UIAKey', null, false);
var expected = "var exact = au.mainApp().getFirstWithPredicateWeighted" +

View File

@@ -1,124 +0,0 @@
// Run with mocha by installing dev deps: npm install --dev
// more docs on writing tests with mocha can be found here:
// http://visionmedia.github.com/mocha/
"use strict";
var _ = require('underscore')
, au = require('../../lib/xpath.js');
describe("XPath lookups", function () {
var oks = {
"//button": {path: [{node: 'button', search: 'desc', index: null}]}
, "//button[last()]": {path: [{node: 'button', search: 'desc', index: -1}]}
, "//button[1]": {path: [{node: 'button', search: 'desc', index: 1}]}
, "/button": {path: [{node: 'button', search: 'child', index: null}]}
, "/but.ton": {path: [{node: 'but.ton', search: 'child', index: null}]}
, "/button[2]": {path: [{node: 'button', search: 'child', index: 2}]}
, "button": {path: [{node: 'button', search: 'desc', index: null}]}
, "//button/text/webview": {path: [
{node: 'button', search: 'desc', index: null}
, {node: 'text', search: 'child', index: null}
, {node: 'webview', search: 'child', index: null}
]}
, "//button[1]/text/webview[3]": {path: [
{node: 'button', search: 'desc', index: 1}
, {node: 'text', search: 'child', index: null}
, {node: 'webview', search: 'child', index: 3}
]}
, "text/webview//button": {path: [
{node: 'text', search: 'desc', index: null}
, {node: 'webview', search: 'child', index: null}
, {node: 'button', search: 'desc', index: null}
]}
, "//button[@name='hi there']": {
attr: 'name',
constraint: 'hi there',
substr: false
}
, "//button[@other_attr='hi there']": {
attr: 'other_attr',
constraint: 'hi there',
substr: false
}
, '//button[@name="hi there"]': {
attr: 'name',
constraint: 'hi there',
substr: false
}
, '//list/button[@name="hi there"]': {
attr: 'name',
constraint: 'hi there',
substr: false
}
, '//button[@name=hi there]': {
attr: 'name',
constraint: 'hi there',
substr: false
}
, '//button[contains(@label, "hi")]': {
attr: 'label',
constraint: 'hi',
substr: true
}
, '//button[contains(@other_attr, "hi")]': {
attr: 'other_attr',
constraint: 'hi',
substr: true
}
, "//button[contains(@label, 'hi')]": {
attr: 'label',
constraint: 'hi',
substr: true
}
, "//button[contains(@label, what's up dog)]": {
attr: 'label',
constraint: "what's up dog",
substr: true
}
, "//*[contains(@text, 'agree')]": {
attr: 'text',
constraint: 'agree',
substr: true
}
, "//*[@text='agree']": {
attr: 'text',
constraint: 'agree',
substr: false
}
};
var notOks = [
, "//button123"
, "//button[-1]"
, "//button[last]"
, "//button[last(]"
, "//button[@name$='hi']"
, "//tag_name"
, "//button[0]"
, "//button[]"
, "//button]"
, "//button["
, "//button[something(@name, 'hi')]"
, "//button[noat='wut']"
, "//button/label[@name='hi']/moar"
, "//@attr"
];
describe("Valid XPaths", function () {
_.each(oks, function (test, xpath) {
it(xpath + " should work", function () {
var parsed = au.parseXpath(xpath);
parsed.should.not.equal(false);
_.each(test, function (val, key) {
parsed[key].should.eql(test[key]);
});
});
});
});
describe("Invalid Xpaths", function () {
_.each(notOks, function (xpath) {
it(xpath + " should not work", function () {
var parsed = au.parseXpath(xpath);
parsed.should.equal(false);
});
});
});
});