Skip to content

Commit e044b7c

Browse files
committed
Merge pull request #62 from prattpratt/master
Return MobileElement directly from AppiumDriver.findElement?() and findElements?() methods
2 parents 2015975 + 0f819c0 commit e044b7c

15 files changed

+334
-148
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@ target/classes/*
66
target/test-classes/*
77
target/surefire-reports/*
88
settings.xml
9+
10+
# Eclipse
11+
/bin
12+
/target
13+
/.settings
14+
.classpath
15+
.project

src/main/java/io/appium/java_client/AppiumDriver.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919

2020
import com.google.common.collect.ImmutableList;
2121
import com.google.common.collect.ImmutableMap;
22+
2223
import org.openqa.selenium.*;
2324
import org.openqa.selenium.remote.*;
2425

26+
import io.appium.java_client.internal.JsonToMobileElementConverter;
27+
2528
import javax.xml.bind.DatatypeConverter;
29+
2630
import java.net.URL;
2731
import java.util.LinkedHashSet;
2832
import java.util.List;
@@ -41,6 +45,7 @@ public class AppiumDriver extends RemoteWebDriver implements MobileDriver, Conte
4145
public AppiumDriver(URL remoteAddress, Capabilities desiredCapabilities){
4246

4347
super(remoteAddress, desiredCapabilities);
48+
this.setElementConverter(new JsonToMobileElementConverter(this));
4449

4550
this.remoteAddress = remoteAddress;
4651
complexFind = new ComplexFind(this);
@@ -404,10 +409,9 @@ public void zoom(int x, int y) {
404409
* @return The textfield with the given accessibility id
405410
*/
406411
public WebElement getNamedTextField(String name) {
407-
RemoteWebElement element = (RemoteWebElement) findElementByAccessibilityId(name);
412+
MobileElement element = (MobileElement) findElementByAccessibilityId(name);
408413
if (element.getTagName() != "TextField") {
409-
MobileElement mobileElement = new MobileElement(element, this);
410-
return mobileElement.findElementByAccessibilityId(name);
414+
return element.findElementByAccessibilityId(name);
411415
}
412416

413417
return element;

src/main/java/io/appium/java_client/ComplexFind.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public ComplexFind(AppiumDriver driver) {
2323
* Create a new remote web element.
2424
*/
2525
private MobileElement newElement(final String elementId) {
26-
final MobileElement element = new MobileElement(new RemoteWebElement(), driver);
26+
final MobileElement element = new MobileElement();
2727
element.setParent(driver);
2828
element.setId(elementId);
2929
element.setFileDetector(driver.getFileDetector());

src/main/java/io/appium/java_client/MobileElement.java

Lines changed: 42 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,72 +17,63 @@
1717

1818
package io.appium.java_client;
1919

20-
import com.google.common.collect.ImmutableMap;
20+
import java.util.List;
21+
2122
import org.openqa.selenium.By;
2223
import org.openqa.selenium.Dimension;
2324
import org.openqa.selenium.Point;
2425
import org.openqa.selenium.WebElement;
25-
import org.openqa.selenium.remote.*;
26-
27-
import java.util.List;
28-
import java.util.Map;
26+
import org.openqa.selenium.remote.FileDetector;
27+
import org.openqa.selenium.remote.RemoteWebElement;
2928

30-
public class MobileElement extends RemoteWebElement {
31-
32-
private String foundBy;
33-
protected FileDetector fileDetector;
34-
35-
private WebElement webElement;
36-
private MobileDriver parent;
37-
38-
public MobileElement(RemoteWebElement originalElement, MobileDriver parentDriver) {
39-
40-
webElement = originalElement;
41-
this.id = originalElement.getId();
42-
this.parent = parentDriver;
43-
//The super doesn't need the parent object at ALL, it's terrible that it asks for one, when it only needs it for setFoundBy()
44-
super.setParent(new FakeRemoteWebDriver());
45-
}
29+
import com.google.common.collect.ImmutableMap;
4630

47-
public List<WebElement> findElements(By by) {
48-
return by.findElements(this);
49-
}
31+
public class MobileElement extends RemoteWebElement implements FindsByAccessibilityId, FindsByAndroidUIAutomator,
32+
FindsByIosUIAutomation {
5033

51-
public WebElement findElement(By by) {
52-
return by.findElement(this);
53-
}
34+
protected FileDetector fileDetector;
5435

55-
public WebElement findElementByIosUIAutomation(String using) { return findElement("-ios uiautomation", using); }
36+
public List<WebElement> findElements(By by) {
37+
return by.findElements(this);
38+
}
5639

57-
public List<WebElement> findElementsByIosUIAutomation(String using) { return findElements("-ios uiautomation", using); }
40+
public WebElement findElement(By by) {
41+
return by.findElement(this);
42+
}
5843

59-
public WebElement findElementByAndroidUIAutomator(String using) { return findElement("-android uiautomator", using); }
44+
public WebElement findElementByIosUIAutomation(String using) {
45+
return findElement("-ios uiautomation", using);
46+
}
6047

61-
public List<WebElement> findElementsByAndroidUIAutomator(String using) { return findElements("-android uiautomator", using); }
48+
public List<WebElement> findElementsByIosUIAutomation(String using) {
49+
return findElements("-ios uiautomation", using);
50+
}
6251

63-
public WebElement findElementByAccessibilityId(String using) { return findElement("accessibility id", using); }
52+
public WebElement findElementByAndroidUIAutomator(String using) {
53+
return findElement("-android uiautomator", using);
54+
}
6455

65-
public List<WebElement> findElementsByAccessibilityId(String using) { return findElements("accessibility id", using); }
56+
public List<WebElement> findElementsByAndroidUIAutomator(String using) {
57+
return findElements("-android uiautomator", using);
58+
}
6659

67-
public void setValue(String value) {
68-
ImmutableMap.Builder builder = ImmutableMap.builder();
69-
builder.put("id", id).put("value", value);
70-
execute(MobileCommand.SET_VALUE, builder.build());
71-
}
60+
public WebElement findElementByAccessibilityId(String using) {
61+
return findElement("accessibility id", using);
62+
}
7263

73-
public Point getCenter() {
74-
Point upperLeft = this.getLocation();
75-
Dimension dimensions = this.getSize();
76-
return new Point(upperLeft.getX() + dimensions.getWidth()/2, upperLeft.getY() + dimensions.getHeight()/2);
77-
}
64+
public List<WebElement> findElementsByAccessibilityId(String using) {
65+
return findElements("accessibility id", using);
66+
}
7867

79-
protected Response execute(String command, Map<String, ?> parameters) {
80-
return parent.execute(command, parameters);
81-
}
68+
public void setValue(String value) {
69+
ImmutableMap.Builder builder = ImmutableMap.builder();
70+
builder.put("id", id).put("value", value);
71+
execute(MobileCommand.SET_VALUE, builder.build());
72+
}
8273

83-
private class FakeRemoteWebDriver extends RemoteWebDriver {
84-
public FakeRemoteWebDriver() {
85-
//hahaha do NOTHING!
86-
}
87-
}
74+
public Point getCenter() {
75+
Point upperLeft = this.getLocation();
76+
Dimension dimensions = this.getSize();
77+
return new Point(upperLeft.getX() + dimensions.getWidth() / 2, upperLeft.getY() + dimensions.getHeight() / 2);
78+
}
8879
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package io.appium.java_client.internal;
2+
3+
import io.appium.java_client.AppiumDriver;
4+
import io.appium.java_client.MobileElement;
5+
6+
import java.util.Collection;
7+
import java.util.Map;
8+
9+
import org.openqa.selenium.WebElement;
10+
import org.openqa.selenium.remote.internal.JsonToWebElementConverter;
11+
12+
import com.google.common.collect.Iterables;
13+
import com.google.common.collect.Lists;
14+
import com.google.common.collect.Maps;
15+
16+
/**
17+
* Reconstitutes {@link WebElement}s from their JSON representation. Will recursively convert Lists
18+
* and Maps to catch nested references. All other values pass through the converter unchanged.
19+
*/
20+
public class JsonToMobileElementConverter extends JsonToWebElementConverter {
21+
private AppiumDriver driver;
22+
23+
public JsonToMobileElementConverter(AppiumDriver driver) {
24+
super(driver);
25+
this.driver = driver;
26+
}
27+
28+
public Object apply(Object result) {
29+
if (result instanceof Collection<?>) {
30+
Collection<?> results = (Collection<?>) result;
31+
return Lists.newArrayList(Iterables.transform(results, this));
32+
}
33+
34+
if (result instanceof Map<?, ?>) {
35+
Map<?, ?> resultAsMap = (Map<?, ?>) result;
36+
if (resultAsMap.containsKey("ELEMENT")) {
37+
MobileElement element = newMobileElement();
38+
element.setId(String.valueOf(resultAsMap.get("ELEMENT")));
39+
element.setFileDetector(driver.getFileDetector());
40+
return element;
41+
} else {
42+
return Maps.transformValues(resultAsMap, this);
43+
}
44+
}
45+
46+
if (result instanceof Number) {
47+
if (result instanceof Float || result instanceof Double) {
48+
return ((Number) result).doubleValue();
49+
}
50+
return ((Number) result).longValue();
51+
}
52+
53+
return result;
54+
}
55+
56+
protected MobileElement newMobileElement() {
57+
MobileElement toReturn = new MobileElement();
58+
toReturn.setParent(driver);
59+
return toReturn;
60+
}
61+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package io.appium.java_client.internal;
2+
3+
import io.appium.java_client.MobileElement;
4+
5+
import java.util.Collection;
6+
import java.util.Map;
7+
8+
import org.openqa.selenium.internal.WrapsElement;
9+
import org.openqa.selenium.remote.RemoteWebElement;
10+
import org.openqa.selenium.remote.internal.WebElementToJsonConverter;
11+
12+
import com.google.common.collect.Collections2;
13+
import com.google.common.collect.ImmutableMap;
14+
import com.google.common.collect.Lists;
15+
import com.google.common.collect.Maps;
16+
17+
/**
18+
* Converts {@link RemoteWebElement} objects, which may be
19+
* {@link WrapsElement wrapped}, into their JSON representation as defined by
20+
* the WebDriver wire protocol. This class will recursively convert Lists and
21+
* Maps to catch nested references.
22+
*
23+
* @see <a href="http://code.google.com/p/selenium/wiki/JsonWireProtocol#WebElement_JSON_Object">
24+
* WebDriver JSON Wire Protocol</a>
25+
*/
26+
public class MobileElementToJsonConverter extends WebElementToJsonConverter {
27+
28+
public Object apply(Object arg) {
29+
if (arg == null || arg instanceof String || arg instanceof Boolean ||
30+
arg instanceof Number) {
31+
return arg;
32+
}
33+
34+
while (arg instanceof WrapsElement) {
35+
arg = ((WrapsElement) arg).getWrappedElement();
36+
}
37+
38+
if (arg instanceof MobileElement) {
39+
return ImmutableMap.of("ELEMENT", ((MobileElement) arg).getId());
40+
}
41+
42+
if (arg.getClass().isArray()) {
43+
arg = Lists.newArrayList((Object[]) arg);
44+
}
45+
46+
if (arg instanceof Collection<?>) {
47+
Collection<?> args = (Collection<?>) arg;
48+
return Collections2.transform(args, this);
49+
}
50+
51+
if (arg instanceof Map<?, ?>) {
52+
Map<?, ?> args = (Map<?, ?>) arg;
53+
Map<String, Object> converted = Maps.newHashMapWithExpectedSize(args.size());
54+
for (Map.Entry<?, ?> entry : args.entrySet()) {
55+
Object key = entry.getKey();
56+
if (!(key instanceof String)) {
57+
throw new IllegalArgumentException(
58+
"All keys in Map script arguments must be strings: " + key.getClass().getName());
59+
}
60+
converted.put((String) key, apply(entry.getValue()));
61+
}
62+
return converted;
63+
}
64+
65+
throw new IllegalArgumentException("Argument is of an illegal type: "
66+
+ arg.getClass().getName());
67+
}
68+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.appium.java_client.remote;
2+
3+
import org.openqa.selenium.remote.CapabilityType;
4+
5+
public interface MobileCapabilityType extends CapabilityType {
6+
7+
String AUTOMATION_NAME = "automationName";
8+
9+
String PLATFORM_NAME = "platformName";
10+
String PLATFORM_VERSION = "platformVersion";
11+
12+
String DEVICE_NAME = "deviceName";
13+
14+
String NEW_COMMAND_TIMEOUT = "newCommandTimeout";
15+
String DEVICE_READY_TIMEOUT = "deviceReadyTimeout";
16+
String LAUNCH_TIMEOUT = "launchTimeout";
17+
18+
String APP = "app";
19+
String APP_PACKAGE = "appPackage";
20+
String APP_ACTIVITY = "appActivity";
21+
}

src/test/java/io/appium/java_client/AccessibilityIdTest.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
package io.appium.java_client;
22

3-
import org.junit.After;
4-
import org.junit.Before;
5-
import org.junit.Test;
6-
import org.openqa.selenium.WebElement;
7-
import org.openqa.selenium.remote.CapabilityType;
8-
import org.openqa.selenium.remote.DesiredCapabilities;
3+
import static org.junit.Assert.assertNotNull;
4+
import static org.junit.Assert.assertTrue;
5+
import io.appium.java_client.remote.MobileCapabilityType;
96

107
import java.io.File;
118
import java.net.URL;
129
import java.util.List;
1310

14-
import static org.junit.Assert.assertNotNull;
15-
import static org.junit.Assert.assertTrue;
11+
import org.junit.After;
12+
import org.junit.Before;
13+
import org.junit.Test;
14+
import org.openqa.selenium.WebElement;
15+
import org.openqa.selenium.remote.DesiredCapabilities;
1616

1717
/**
1818
* Test context-related features
@@ -26,11 +26,11 @@ public void setup() throws Exception {
2626
File appDir = new File("src/test/java/io/appium/java_client");
2727
File app = new File(appDir, "UICatalog.app.zip");
2828
DesiredCapabilities capabilities = new DesiredCapabilities();
29-
capabilities.setCapability(CapabilityType.BROWSER_NAME, "");
30-
capabilities.setCapability("platformVersion", "7.1");
31-
capabilities.setCapability("platformName", "iOS");
32-
capabilities.setCapability("deviceName", "iPhone Simulator");
33-
capabilities.setCapability("app", app.getAbsolutePath());
29+
capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, "");
30+
capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "7.1");
31+
capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, "iOS");
32+
capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "iPhone Simulator");
33+
capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
3434
driver = new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
3535
}
3636

0 commit comments

Comments
 (0)