Skip to content

Commit

Permalink
getPageSource now uses the same code as the java Xpath code. Happens …
Browse files Browse the repository at this point in the history
…on the java side, not in node.
  • Loading branch information
Jonahss committed Aug 21, 2014
1 parent 350f8c4 commit 4f3a7eb
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 143 deletions.
59 changes: 1 addition & 58 deletions lib/devices/android/android-controller.js
Expand Up @@ -7,13 +7,11 @@ var errors = require('../../server/errors.js')
, helpers = require('../../helpers.js')
, status = require('../../server/status.js')
, NotYetImplementedError = errors.NotYetImplementedError
, exec = require('child_process').exec
, fs = require('fs')
, temp = require('temp')
, async = require('async')
, mkdirp = require('mkdirp')
, path = require('path')
, XMLDom = require("xmldom")
, AdmZip = require("adm-zip")
, helpers = require('../../helpers.js')
, Args = require("vargs").Constructor;
Expand Down Expand Up @@ -254,15 +252,6 @@ androidController.getCssProperty = function (elementId, propertyName, cb) {
cb(new NotYetImplementedError(), null);
};

var _updateSourceXMLNodeNames = function (source) {
var newSource;
var origDom = new XMLDom.DOMParser().parseFromString(source);
var newDom = new XMLDom.DOMImplementation().createDocument(null);
_annotateXmlNodes(newDom, newDom, origDom);
newSource = new XMLDom.XMLSerializer().serializeToString(newDom);
return newSource;
};

var _getNodeClass = function (node) {
var nodeClass = null;
_.each(node.attributes, function (attr) {
Expand Down Expand Up @@ -307,53 +296,7 @@ var _annotateXmlNodes = function (newDom, newParent, oldNode, instances) {
};

androidController.getPageSource = function (cb) {
var xmlFile = temp.path({suffix: '.xml'});
var onDeviceXmlPath = this.dataDir + '/local/tmp/dump.xml';
async.series(
[
function (cb) {
this.proxy(["dumpWindowHierarchy"], cb);
}.bind(this),
function (cb) {
var cmd = this.adb.adbCmd + ' pull ' + onDeviceXmlPath + ' "' + xmlFile + '"';
logger.debug('transferPageSourceXML command: ' + cmd);
exec(cmd, { maxBuffer: 524288 }, function (err, stdout, stderr) {
if (err) {
logger.error(stderr);
return cb(err);
}
cb(null);
});
}.bind(this)

],
// Top level cb
function (err) {
if (err) return cb(err);
var xml = '';
if (fs.existsSync(xmlFile)) {
xml = fs.readFileSync(xmlFile, 'utf8');
fs.unlinkSync(xmlFile);
}

// xml file may not exist or it could be empty.
if (xml === '') {
var error = "dumpWindowHierarchy failed";
logger.error(error);
return cb(error);
}

try {
xml = _updateSourceXMLNodeNames(xml);
} catch (e) {
logger.error(e);
return cb(e);
}
cb(null, {
status: status.codes.Success.code
, value: xml
});
});
this.proxy(["source", {}], cb);
};

androidController.getAlertText = function (cb) {
Expand Down
Expand Up @@ -38,14 +38,14 @@ class AndroidCommandExecutor {
map.put("getSize", new GetSize());
map.put("wake", new Wake());
map.put("pressBack", new PressBack());
map.put("dumpWindowHierarchy", new DumpWindowHierarchy());
map.put("pressKeyCode", new PressKeyCode());
map.put("longPressKeyCode", new LongPressKeyCode());
map.put("takeScreenshot", new TakeScreenshot());
map.put("updateStrings", new UpdateStrings());
map.put("getDataDir", new GetDataDir());
map.put("performMultiPointerGesture", new MultiPointerGesture());
map.put("openNotification", new OpenNotification());
map.put("source", new Source());
}

/**
Expand Down
Expand Up @@ -165,7 +165,8 @@ private String runCommand(final AndroidCommand cmd) {
} else if (cmd.commandType() == AndroidCommandType.ACTION) {
try {
res = executor.execute(cmd);
} catch (final Exception e) {
} catch (final Exception e) { // Here we catch all possible exceptions and return a JSON Wire Protocol UnknownError
// This prevents exceptions from halting the bootstrap app
res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
}
} else {
Expand Down

This file was deleted.

@@ -0,0 +1,47 @@
package io.appium.android.bootstrap.handler;

import io.appium.android.bootstrap.AndroidCommand;
import io.appium.android.bootstrap.AndroidCommandResult;
import io.appium.android.bootstrap.CommandHandler;
import io.appium.android.bootstrap.utils.XMLHierarchy;
import org.json.JSONException;
import org.w3c.dom.Document;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;

/**
* Get page source. Return as string of XML doc
*/
public class Source extends CommandHandler {
@Override
public AndroidCommandResult execute(AndroidCommand command) throws JSONException {

Document doc = (Document) XMLHierarchy.getFormattedXMLDoc();

TransformerFactory tf = TransformerFactory.newInstance();
StringWriter writer = new StringWriter();
Transformer transformer;
String xmlString;


try {
transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
xmlString = writer.getBuffer().toString();

} catch (TransformerConfigurationException e) {
e.printStackTrace();
throw new RuntimeException("Something went terribly wrong while converting xml document to string");
} catch (TransformerException e) {
return getErrorResult("Could not parse xml hierarchy to string: " + e.getMessage());
}

return getSuccessResult(xmlString);
}
}
Expand Up @@ -3,7 +3,7 @@
import com.android.uiautomator.core.UiSelector;

/**
* Created by jonahss on 8/12/14.
* Simple class for holding a String 2-tuple. An android class, and instance number, used for finding elements by xpath.
*/
public class ClassInstancePair {

Expand Down
Expand Up @@ -36,11 +36,7 @@ public static ArrayList<ClassInstancePair> getClassInstancePairs(String xpathExp

Node formattedXmlRoot;

try {
formattedXmlRoot = getFormattedXMLDoc();
} catch (XPathExpressionException e) {
throw new InvalidSelectorException(e.getMessage());
}
formattedXmlRoot = getFormattedXMLDoc();

return getClassInstancePairs(exp, formattedXmlRoot);
}
Expand All @@ -60,16 +56,14 @@ public static ArrayList<ClassInstancePair> getClassInstancePairs(XPathExpression
if (nodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
try {
pairs.add(getPairFromNode(nodes.item(i)));
} catch (PairCreationException e) {
continue;
}
} catch (PairCreationException e) { }
}
}

return pairs;
}

public static InputSource getRawXMLHierarchy() throws ElementNotFoundException {
public static InputSource getRawXMLHierarchy() {
// Note that
// "new File(new File(Environment.getDataDirectory(), "local/tmp"), fileName)"
// is directly from the UiDevice.java source code.
Expand Down Expand Up @@ -97,18 +91,23 @@ public static InputSource getRawXMLHierarchy() throws ElementNotFoundException {
return new InputSource(new FileReader(dumpFile));
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new ElementNotFoundException("Failed to Dump Window Hierarchy");
throw new RuntimeException("Failed to Dump Window Hierarchy");
}
}

public static Node getFormattedXMLDoc() throws ElementNotFoundException, XPathExpressionException, ParserConfigurationException {
public static Node getFormattedXMLDoc() {
return formatXMLInput(getRawXMLHierarchy());
}

public static Node formatXMLInput(InputSource input) throws XPathExpressionException {
public static Node formatXMLInput(InputSource input) {
XPath xpath = XPathFactory.newInstance().newXPath();

Node root = (Node) xpath.evaluate("/", input, XPathConstants.NODE);
Node root = null;
try {
root = (Node) xpath.evaluate("/", input, XPathConstants.NODE);
} catch (XPathExpressionException e) {
throw new RuntimeException("Could not read xml hierarchy: " + e.getMessage());
}

HashMap<String, Integer> instances = new HashMap<String, Integer>();

Expand Down
@@ -1,14 +1,20 @@
package io.appium.android.bootstrap.utils;

import junit.framework.TestCase;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;

public class XMLHierarchyTest extends TestCase {
Expand Down Expand Up @@ -49,4 +55,25 @@ public void testFormatXMLInput() throws Exception {
assertEquals("2", childNode.getAttributes().getNamedItem("instance").getNodeValue());

}

public void testOutput() throws Exception {
String xmlString = "<?xml version='1.0' encoding='UTF-8' standalone='yes'?><hierarchy rotation=\"0\"><android.widget.FrameLayout index=\"0\" text=\"\" resource-id=\"\" class=\"android.widget.FrameLayout\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[0,0][480,800]\" instance=\"0\"><android.view.View index=\"0\" text=\"\" resource-id=\"android:id/action_bar\" class=\"android.view.View\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[0,38][480,110]\" instance=\"0\"><android.widget.TextView index=\"0\" text=\"TestApp\" resource-id=\"android:id/action_bar_title\" class=\"android.widget.TextView\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[73,55][173,92]\" instance=\"0\"/><android.widget.ImageButton index=\"1\" text=\"\" resource-id=\"\" class=\"android.widget.ImageButton\" package=\"com.droidzilla.testapp\" content-desc=\"More options\" checkable=\"false\" checked=\"false\" clickable=\"true\" enabled=\"true\" focusable=\"true\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[396,38][480,110]\" instance=\"0\"/></android.view.View><android.widget.GridView index=\"1\" text=\"\" resource-id=\"com.droidzilla.testapp:id/cm_course_books_grid\" class=\"android.widget.GridView\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"true\" enabled=\"true\" focusable=\"true\" focused=\"true\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[3,113][477,797]\" instance=\"0\"><android.widget.RelativeLayout index=\"0\" text=\"\" resource-id=\"\" class=\"android.widget.RelativeLayout\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"true\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[3,113][238,413]\" instance=\"0\"><android.widget.TextView index=\"0\" text=\"Image 0\" resource-id=\"com.droidzilla.testapp:id/bookStatus\" class=\"android.widget.TextView\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[82,136][158,165]\" instance=\"1\"/><android.widget.TextView index=\"1\" text=\"John\" resource-id=\"com.droidzilla.testapp:id/bookTitle\" class=\"android.widget.TextView\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[96,361][144,390]\" instance=\"2\"/></android.widget.RelativeLayout><android.widget.RelativeLayout index=\"1\" text=\"\" resource-id=\"\" class=\"android.widget.RelativeLayout\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"true\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[241,113][476,413]\" instance=\"1\"><android.widget.TextView index=\"0\" text=\"Image 1\" resource-id=\"com.droidzilla.testapp:id/bookStatus\" class=\"android.widget.TextView\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[320,136][396,165]\" instance=\"3\"/><android.widget.TextView index=\"1\" text=\"Dan\" resource-id=\"com.droidzilla.testapp:id/bookTitle\" class=\"android.widget.TextView\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[339,361][377,390]\" instance=\"4\"/></android.widget.RelativeLayout><android.widget.RelativeLayout index=\"2\" text=\"\" resource-id=\"\" class=\"android.widget.RelativeLayout\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"true\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[3,416][238,716]\" instance=\"2\"><android.widget.TextView index=\"0\" text=\"Image 2\" resource-id=\"com.droidzilla.testapp:id/bookStatus\" class=\"android.widget.TextView\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[82,439][158,468]\" instance=\"5\"/><android.widget.TextView index=\"1\" text=\"Mary\" resource-id=\"com.droidzilla.testapp:id/bookTitle\" class=\"android.widget.TextView\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[96,664][145,693]\" instance=\"6\"/></android.widget.RelativeLayout><android.widget.RelativeLayout index=\"3\" text=\"\" resource-id=\"\" class=\"android.widget.RelativeLayout\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"true\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[241,416][476,716]\" instance=\"3\"><android.widget.TextView index=\"0\" text=\"Image 3\" resource-id=\"com.droidzilla.testapp:id/bookStatus\" class=\"android.widget.TextView\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[320,439][396,468]\" instance=\"7\"/><android.widget.TextView index=\"1\" text=\"Diana\" resource-id=\"com.droidzilla.testapp:id/bookTitle\" class=\"android.widget.TextView\" package=\"com.droidzilla.testapp\" content-desc=\"\" checkable=\"false\" checked=\"false\" clickable=\"false\" enabled=\"true\" focusable=\"false\" focused=\"false\" scrollable=\"false\" long-clickable=\"false\" password=\"false\" selected=\"false\" bounds=\"[331,664][386,693]\" instance=\"8\"/></android.widget.RelativeLayout></android.widget.GridView></android.widget.FrameLayout></hierarchy>\n";
InputSource testInput = new InputSource(new StringReader(xmlString));

Node node = XMLHierarchy.formatXMLInput(testInput);
Document doc = (Document) node;

TransformerFactory tf = TransformerFactory.newInstance();
StringWriter writer = new StringWriter();
Transformer transformer;
String out;

transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
out = writer.getBuffer().toString();

assertEquals(xmlString, out); //close enough


}
}
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -92,7 +92,8 @@
"winston": "~0.7.3",
"ws": "~0.4.31",
"xml2js": "~0.4.4",
"xmldom": "~0.1.19"
"xmldom": "~0.1.19",
"xpath": "0.0.7"
},
"scripts": {
"test": "grunt travis"
Expand Down
6 changes: 3 additions & 3 deletions test/functional/android/apidemos/source-specs.js
Expand Up @@ -18,19 +18,19 @@ describe("apidemos - source", function () {

it('should return the page source', function (done) {
driver
.elementByNameOrNull('Accessibility') // waiting for page to load
.elementByAccessibilityId('Animation') // waiting for page to load
.source()
.then(function (source) {
assertSource(source);
}).nodeify(done);
});
it('should return the page source without crashing other commands', function (done) {
driver
.complexFind([[[3, "Animation"]]])
.elementByAccessibilityId('Animation')
.source().then(function (source) {
assertSource(source);
})
.complexFind([[[3, "Animation"]]])
.elementByAccessibilityId('Animation')
.nodeify(done);
});
});

0 comments on commit 4f3a7eb

Please sign in to comment.