ByXPath.java revision eb4e1921c193bb90eb0122ea7b0fd37cef60e8e1
1c134b29628280a680fdf2696480166de56074b1eKevin Jin/* 2c134b29628280a680fdf2696480166de56074b1eKevin Jin * Copyright (C) 2013 DroidDriver committers 3c134b29628280a680fdf2696480166de56074b1eKevin Jin * 4c134b29628280a680fdf2696480166de56074b1eKevin Jin * Licensed under the Apache License, Version 2.0 (the "License"); 5c134b29628280a680fdf2696480166de56074b1eKevin Jin * you may not use this file except in compliance with the License. 6c134b29628280a680fdf2696480166de56074b1eKevin Jin * You may obtain a copy of the License at 7c134b29628280a680fdf2696480166de56074b1eKevin Jin * 8c134b29628280a680fdf2696480166de56074b1eKevin Jin * http://www.apache.org/licenses/LICENSE-2.0 9c134b29628280a680fdf2696480166de56074b1eKevin Jin * 10c134b29628280a680fdf2696480166de56074b1eKevin Jin * Unless required by applicable law or agreed to in writing, software 11c134b29628280a680fdf2696480166de56074b1eKevin Jin * distributed under the License is distributed on an "AS IS" BASIS, 12c134b29628280a680fdf2696480166de56074b1eKevin Jin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c134b29628280a680fdf2696480166de56074b1eKevin Jin * See the License for the specific language governing permissions and 14c134b29628280a680fdf2696480166de56074b1eKevin Jin * limitations under the License. 15c134b29628280a680fdf2696480166de56074b1eKevin Jin */ 167576fbbba2bf515908b45293b7156b5bfe088938Kevin Jinpackage com.google.android.droiddriver.finders; 17c134b29628280a680fdf2696480166de56074b1eKevin Jin 1879884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport android.util.Log; 1979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 2079884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport com.google.android.droiddriver.UiElement; 2179884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport com.google.android.droiddriver.base.AbstractUiElement; 22c134b29628280a680fdf2696480166de56074b1eKevin Jinimport com.google.android.droiddriver.exceptions.DroidDriverException; 2379884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport com.google.android.droiddriver.exceptions.ElementNotFoundException; 246316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport com.google.android.droiddriver.util.FileUtils; 2579884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport com.google.android.droiddriver.util.Logs; 26c134b29628280a680fdf2696480166de56074b1eKevin Jinimport com.google.common.base.Objects; 27c134b29628280a680fdf2696480166de56074b1eKevin Jinimport com.google.common.base.Preconditions; 28c134b29628280a680fdf2696480166de56074b1eKevin Jin 2979884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport org.w3c.dom.DOMException; 3079884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport org.w3c.dom.Document; 3179884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport org.w3c.dom.Element; 3279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 336316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport java.io.BufferedOutputStream; 346316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin 3579884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport javax.xml.parsers.DocumentBuilderFactory; 3679884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport javax.xml.parsers.ParserConfigurationException; 376316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport javax.xml.transform.OutputKeys; 386316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport javax.xml.transform.Transformer; 396316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport javax.xml.transform.TransformerFactory; 406316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport javax.xml.transform.dom.DOMSource; 416316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport javax.xml.transform.stream.StreamResult; 42fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jinimport javax.xml.xpath.XPath; 4379884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport javax.xml.xpath.XPathConstants; 44c134b29628280a680fdf2696480166de56074b1eKevin Jinimport javax.xml.xpath.XPathExpression; 45c134b29628280a680fdf2696480166de56074b1eKevin Jinimport javax.xml.xpath.XPathExpressionException; 46c134b29628280a680fdf2696480166de56074b1eKevin Jinimport javax.xml.xpath.XPathFactory; 47c134b29628280a680fdf2696480166de56074b1eKevin Jin 48fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jin/** 49fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jin * Find matching UiElement by XPath. 50fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jin */ 517576fbbba2bf515908b45293b7156b5bfe088938Kevin Jinpublic class ByXPath implements Finder { 52fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jin private static final XPath XPATH_COMPILER = XPathFactory.newInstance().newXPath(); 5379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin private static final String UI_ELEMENT = "UiElement"; 5479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin // document needs to be static so that when buildDomNode is called recursively 5579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin // on children they are in the same document to be appended. 5679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin private static Document document; 578e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin private final String xPathString; 58c134b29628280a680fdf2696480166de56074b1eKevin Jin private final XPathExpression xPathExpression; 59c134b29628280a680fdf2696480166de56074b1eKevin Jin 608e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin protected ByXPath(String xPathString) { 618e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin this.xPathString = Preconditions.checkNotNull(xPathString); 62c134b29628280a680fdf2696480166de56074b1eKevin Jin try { 63fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jin xPathExpression = XPATH_COMPILER.compile(xPathString); 64c134b29628280a680fdf2696480166de56074b1eKevin Jin } catch (XPathExpressionException e) { 656316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin throw new DroidDriverException("xPathString=" + xPathString, e); 66c134b29628280a680fdf2696480166de56074b1eKevin Jin } 67c134b29628280a680fdf2696480166de56074b1eKevin Jin } 68c134b29628280a680fdf2696480166de56074b1eKevin Jin 69c134b29628280a680fdf2696480166de56074b1eKevin Jin @Override 70c134b29628280a680fdf2696480166de56074b1eKevin Jin public String toString() { 718e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin return Objects.toStringHelper(this).addValue(xPathString).toString(); 72c134b29628280a680fdf2696480166de56074b1eKevin Jin } 73c134b29628280a680fdf2696480166de56074b1eKevin Jin 7479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin @Override 7579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin public UiElement find(UiElement context) { 7679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin AbstractUiElement contextNode = (AbstractUiElement) context; 7779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin Element domNode = contextNode.getDomNode(); 7879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin try { 7979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin getDocument().appendChild(domNode); 8079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin Element foundNode = (Element) xPathExpression.evaluate(domNode, XPathConstants.NODE); 8179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (foundNode == null) { 826316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin Logs.log(Log.DEBUG, "XPath evaluation returns null for " + xPathString); 8379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin throw new ElementNotFoundException(this); 8479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 85eb4e1921c193bb90eb0122ea7b0fd37cef60e8e1Kevin Jin 86eb4e1921c193bb90eb0122ea7b0fd37cef60e8e1Kevin Jin UiElement match = (UiElement) foundNode.getUserData(UI_ELEMENT); 87eb4e1921c193bb90eb0122ea7b0fd37cef60e8e1Kevin Jin Logs.log(Log.INFO, "Found match: " + match); 88eb4e1921c193bb90eb0122ea7b0fd37cef60e8e1Kevin Jin return match; 8979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } catch (XPathExpressionException e) { 9079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin throw new ElementNotFoundException(this, e); 9179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } finally { 9279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin try { 9379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin getDocument().removeChild(domNode); 9479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } catch (DOMException ignored) { 9579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin document = null; // getDocument will create new 9679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 9779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 9879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 9979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 10079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin private static Document getDocument() { 10179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (document == null) { 10279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin try { 10379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 10479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } catch (ParserConfigurationException e) { 10579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin throw new DroidDriverException(e); 10679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 10779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 10879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin return document; 10979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 11079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 11179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin /** 11279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin * Used internally in {@link AbstractUiElement}. 11379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin */ 11479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin public static Element buildDomNode(AbstractUiElement uiElement) { 11579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin String className = uiElement.getClassName(); 11679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (className == null) { 11779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin className = "UNKNOWN"; 11879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 11979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin Element element = getDocument().createElement(XPaths.tag(className)); 12079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin element.setUserData(UI_ELEMENT, uiElement, null /* UserDataHandler */); 12179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 12279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.CLASS, className); 12379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.RESOURCE_ID, uiElement.getResourceId()); 12479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.PACKAGE, uiElement.getPackageName()); 12579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.CONTENT_DESC, uiElement.getContentDescription()); 12679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.TEXT, uiElement.getText()); 12779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.CHECKABLE, uiElement.isCheckable()); 12879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.CHECKED, uiElement.isChecked()); 12979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.CLICKABLE, uiElement.isClickable()); 13079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.ENABLED, uiElement.isEnabled()); 13179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.FOCUSABLE, uiElement.isFocusable()); 13279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.FOCUSED, uiElement.isFocused()); 13379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.SCROLLABLE, uiElement.isScrollable()); 13479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.LONG_CLICKABLE, uiElement.isLongClickable()); 13579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.PASSWORD, uiElement.isPassword()); 13679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.SELECTED, uiElement.isSelected()); 13779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin element.setAttribute(Attribute.BOUNDS.getName(), uiElement.getBounds().toShortString()); 13879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 13979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin // TODO: visitor pattern 14079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin int childCount = uiElement.getChildCount(); 14179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin for (int i = 0; i < childCount; i++) { 14279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin AbstractUiElement child = uiElement.getChild(i); 14379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (child == null) { 1446316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin Logs.log(Log.WARN, "Skip null child for " + uiElement); 14579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin continue; 14679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 14779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (!child.isVisible()) { 1486316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin Logs.log(Log.VERBOSE, "Skip invisible child: " + child); 14979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin continue; 15079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 15179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 15279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin element.appendChild(child.getDomNode()); 15379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 15479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin return element; 15579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 15679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 15779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin private static void setAttribute(Element element, Attribute attr, String value) { 15879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (value != null) { 15979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin element.setAttribute(attr.getName(), value); 16079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 161c134b29628280a680fdf2696480166de56074b1eKevin Jin } 1628e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin 16379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin // add attribute only if it's true 16479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin private static void setAttribute(Element element, Attribute attr, boolean value) { 16579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (value) { 16679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin element.setAttribute(attr.getName(), ""); 16779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 1688e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin } 1696316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin 1706316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin public static boolean dumpDom(String path, AbstractUiElement uiElement) { 1716316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin BufferedOutputStream bos = null; 1726316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin try { 1736316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin bos = FileUtils.open(path); 1746316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin Transformer transformer = TransformerFactory.newInstance().newTransformer(); 1756316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 1766316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin transformer.transform(new DOMSource(uiElement.getDomNode()), new StreamResult(bos)); 1776316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin Logs.log(Log.INFO, "Wrote dom to " + path); 1786316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } catch (Exception e) { 1796316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin Logs.log(Log.ERROR, e, "Fail to transform node"); 1806316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin return false; 1816316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } finally { 1826316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin if (bos != null) { 1836316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin try { 1846316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin bos.close(); 1856316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } catch (Exception e) { 1866316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin // ignore 1876316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } 1886316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } 1896316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } 1906316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin return true; 1916316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } 192c134b29628280a680fdf2696480166de56074b1eKevin Jin} 193