Skip to content

Commit e2dafc7

Browse files
Repeatable annotations & HasSessionDetails API (#497)
* Migration to Java 8: Repeatable annotations - deprecated annotations: AndroidFindAll, AndroidFindBys, iOSFindAll, iOSFindBys, SelendroidFindAll, SelendroidFindBys. - new annotations were added: AndroidFindBySet, iOSFindBySet, SelendroidFindBySet, HowToUseLocators. * Additional refactoring: Required HasSessionDetails API was added - added interface HasSessionDetails which contains methods with default implementation. It is implemented by AppiumDriver and MobileElement - AndroidDriverTest was modified. * Additional refactoring: MobileElement doesn't implement HasSessionDetails API * Additional refactoring: Migration of page object tools to the HasSessionDetails API was finished * Migration to repeatable annotations is finished. It needs for the testing. * Migration to repeatable page object annotations is finished * chromedriver was updated for the testing * Documentation was updated * Missed javadocs were provided. Checkstyle issues were fixed. * Merge branch 'master' of https://github.com/appium/java-client into repeatable_annotations # Conflicts: # src/main/java/io/appium/java_client/AppiumDriver.java * The bug fix.
1 parent 728d400 commit e2dafc7

38 files changed

+645
-255
lines changed

docs/Page-objects.md

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ List<RemoteWebElement> someElements;
8080

8181
# Also it is possible to define chained or any possible locators.
8282

83-
- Chained
83+
## - Chained
84+
85+
### If you use build versions < 5.x.x
86+
8487
```java
8588
import org.openqa.selenium.remote.RemoteWebElement;
8689
import io.appium.java_client.pagefactory.*;
@@ -98,7 +101,52 @@ RemoteWebElement someElement;
98101
List<RemoteWebElement> someElements;
99102
```
100103

101-
- Any possible
104+
### If you use build versions >= 5.x.x
105+
106+
```java
107+
import org.openqa.selenium.remote.RemoteWebElement;
108+
import io.appium.java_client.pagefactory.*;
109+
import org.openqa.selenium.support.FindBys;
110+
import org.openqa.selenium.support.FindBy;
111+
112+
@FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
113+
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
114+
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
115+
RemoteWebElement someElement;
116+
117+
@FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
118+
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
119+
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
120+
List<RemoteWebElement> someElements;
121+
```
122+
123+
or
124+
125+
```java
126+
import org.openqa.selenium.remote.RemoteWebElement;
127+
import io.appium.java_client.pagefactory.*;
128+
import org.openqa.selenium.support.FindBys;
129+
import org.openqa.selenium.support.FindBy;
130+
131+
import static io.appium.java_client.pagefactory.LocatorGroupStrategy.CHAIN;
132+
133+
@HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = CHAIN)
134+
@FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
135+
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
136+
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
137+
RemoteWebElement someElement;
138+
139+
@HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = CHAIN)
140+
@FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
141+
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
142+
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
143+
List<RemoteWebElement> someElements;
144+
```
145+
146+
## - Any possible
147+
148+
### If you use build versions < 5.x.x
149+
102150
```java
103151
import org.openqa.selenium.remote.RemoteWebElement;
104152
import io.appium.java_client.pagefactory.*;
@@ -116,6 +164,78 @@ RemoteWebElement someElement;
116164
List<RemoteWebElement> someElements;
117165
```
118166

167+
### If you use build versions >= 5.x.x
168+
169+
```java
170+
import org.openqa.selenium.remote.RemoteWebElement;
171+
import io.appium.java_client.pagefactory.*;
172+
import org.openqa.selenium.support.FindBy;
173+
import org.openqa.selenium.support.FindByAll;
174+
175+
import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE;
176+
177+
@HowToUseLocators(androidAutomation = ALL_POSSIBLE, iOSAutomation = ALL_POSSIBLE)
178+
@FindAll{@FindBy(someStrategy1), @FindBy(someStrategy2)})
179+
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
180+
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
181+
RemoteWebElement someElement;
182+
183+
@HowToUseLocators(androidAutomation = ALL_POSSIBLE, iOSAutomation = ALL_POSSIBLE)
184+
@FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
185+
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
186+
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
187+
List<RemoteWebElement> someElements;
188+
```
189+
190+
## Also possible combined variants:
191+
192+
```java
193+
import org.openqa.selenium.remote.RemoteWebElement;
194+
import io.appium.java_client.pagefactory.*;
195+
import org.openqa.selenium.support.FindBy;
196+
import org.openqa.selenium.support.FindByAll;
197+
198+
import static io.appium.java_client.pagefactory.LocatorGroupStrategy.CHAIN;
199+
import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE;
200+
201+
@HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = ALL_POSSIBLE)
202+
@FindAll{@FindBy(someStrategy1), @FindBy(someStrategy2)})
203+
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
204+
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
205+
RemoteWebElement someElement;
206+
207+
@HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = ALL_POSSIBLE)
208+
@FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
209+
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
210+
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
211+
List<RemoteWebElement> someElements;
212+
```
213+
214+
or
215+
216+
```java
217+
import org.openqa.selenium.remote.RemoteWebElement;
218+
import io.appium.java_client.pagefactory.*;
219+
import org.openqa.selenium.support.FindBy;
220+
import org.openqa.selenium.support.FindByAll;
221+
222+
import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE;
223+
224+
@HowToUseLocators(iOSAutomation = ALL_POSSIBLE)
225+
@FindAll{@FindBy(someStrategy1), @FindBy(someStrategy2)})
226+
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2) //this is the chain
227+
//by default
228+
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
229+
RemoteWebElement someElement;
230+
231+
@HowToUseLocators(iOSAutomation = ALL_POSSIBLE)
232+
@FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
233+
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2) //this is the chain
234+
//by default
235+
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
236+
List<RemoteWebElement> someElements;
237+
```
238+
119239
# Appium Java client is integrated with Selenium PageFactory by AppiumFieldDecorator.
120240

121241
Object fields are populated as below:

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

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17-
1817
package io.appium.java_client;
1918

2019
import static com.google.common.base.Preconditions.checkNotNull;
2120

22-
import static io.appium.java_client.MobileCommand.GET_SESSION;
23-
2421
import com.google.common.collect.ImmutableMap;
2522

2623
import io.appium.java_client.remote.AppiumCommandExecutor;
@@ -58,18 +55,17 @@
5855
import java.util.Map;
5956
import java.util.Set;
6057

61-
6258
/**
63-
* @param <T> the required type of class which implement {@link org.openqa.selenium.WebElement}.
64-
* Instances of the defined type will be returned via findElement* and findElements*
65-
* Warning (!!!). Allowed types:
66-
* {@link org.openqa.selenium.WebElement}
67-
* {@link io.appium.java_client.TouchableElement}
68-
* {@link org.openqa.selenium.remote.RemoteWebElement}
69-
* {@link io.appium.java_client.MobileElement} and its subclasses that designed
70-
* specifically
71-
* for each target mobile OS (still Android and iOS)
72-
*/
59+
* @param <T> the required type of class which implement {@link org.openqa.selenium.WebElement}.
60+
* Instances of the defined type will be returned via findElement* and findElements*
61+
* Warning (!!!). Allowed types:
62+
* {@link org.openqa.selenium.WebElement}
63+
* {@link io.appium.java_client.TouchableElement}
64+
* {@link org.openqa.selenium.remote.RemoteWebElement}
65+
* {@link io.appium.java_client.MobileElement} and its subclasses that designed
66+
* specifically
67+
* for each target mobile OS (still Android and iOS)
68+
*/
7369
@SuppressWarnings("unchecked")
7470
public abstract class AppiumDriver<T extends WebElement>
7571
extends DefaultGenericMobileDriver<T> {
@@ -81,13 +77,13 @@ public abstract class AppiumDriver<T extends WebElement>
8177
private ExecuteMethod executeMethod;
8278

8379
/**
84-
* @param executor is an instance of {@link org.openqa.selenium.remote.HttpCommandExecutor}
85-
* or class that extends it. Default commands or another vendor-specific
86-
* commands may be specified there.
87-
* @param capabilities take a look
88-
* at {@link org.openqa.selenium.Capabilities}
80+
* @param executor is an instance of {@link org.openqa.selenium.remote.HttpCommandExecutor}
81+
* or class that extends it. Default commands or another vendor-specific
82+
* commands may be specified there.
83+
* @param capabilities take a look
84+
* at {@link org.openqa.selenium.Capabilities}
8985
* @param converterClazz is an instance of a class that extends
90-
* {@link org.openqa.selenium.remote.internal.JsonToWebElementConverter}. It converts
86+
* {@link org.openqa.selenium.remote.internal.JsonToWebElementConverter}. It converts
9187
* JSON response to an instance of
9288
* {@link org.openqa.selenium.WebElement}
9389
*/
@@ -100,10 +96,10 @@ protected AppiumDriver(HttpCommandExecutor executor, Capabilities capabilities,
10096
this.remoteAddress = executor.getAddressOfRemoteServer();
10197
try {
10298
Constructor<? extends JsonToWebElementConverter> constructor =
103-
converterClazz.getConstructor(RemoteWebDriver.class);
99+
converterClazz.getConstructor(RemoteWebDriver.class);
104100
this.setElementConverter(constructor.newInstance(this));
105101
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException
106-
| InvocationTargetException e) {
102+
| InvocationTargetException e) {
107103
throw new RuntimeException(e);
108104
}
109105
}
@@ -116,7 +112,7 @@ public AppiumDriver(URL remoteAddress, Capabilities desiredCapabilities,
116112

117113
public AppiumDriver(URL remoteAddress, HttpClient.Factory httpClientFactory,
118114
Capabilities desiredCapabilities,
119-
Class<? extends JsonToWebElementConverter> converterClazz) {
115+
Class<? extends JsonToWebElementConverter> converterClazz) {
120116
this(new AppiumCommandExecutor(MobileCommand.commandRepository, remoteAddress,
121117
httpClientFactory), desiredCapabilities, converterClazz);
122118
}
@@ -129,7 +125,7 @@ public AppiumDriver(AppiumDriverLocalService service, Capabilities desiredCapabi
129125

130126
public AppiumDriver(AppiumDriverLocalService service, HttpClient.Factory httpClientFactory,
131127
Capabilities desiredCapabilities,
132-
Class<? extends JsonToWebElementConverter> converterClazz) {
128+
Class<? extends JsonToWebElementConverter> converterClazz) {
133129
this(new AppiumCommandExecutor(MobileCommand.commandRepository, service, httpClientFactory),
134130
desiredCapabilities, converterClazz);
135131
}
@@ -141,14 +137,14 @@ public AppiumDriver(AppiumServiceBuilder builder, Capabilities desiredCapabiliti
141137

142138
public AppiumDriver(AppiumServiceBuilder builder, HttpClient.Factory httpClientFactory,
143139
Capabilities desiredCapabilities,
144-
Class<? extends JsonToWebElementConverter> converterClazz) {
140+
Class<? extends JsonToWebElementConverter> converterClazz) {
145141
this(builder.build(), httpClientFactory, desiredCapabilities, converterClazz);
146142
}
147143

148144
public AppiumDriver(HttpClient.Factory httpClientFactory, Capabilities desiredCapabilities,
149145
Class<? extends JsonToWebElementConverter> converterClazz) {
150-
this(AppiumDriverLocalService.buildDefaultService(), httpClientFactory, desiredCapabilities,
151-
converterClazz);
146+
this(AppiumDriverLocalService.buildDefaultService(), httpClientFactory,
147+
desiredCapabilities, converterClazz);
152148
}
153149

154150
public AppiumDriver(Capabilities desiredCapabilities,
@@ -158,8 +154,8 @@ public AppiumDriver(Capabilities desiredCapabilities,
158154

159155
/**
160156
* @param originalCapabilities the given {@link Capabilities}.
161-
* @param newPlatform a {@link MobileCapabilityType#PLATFORM_NAME} value which has
162-
* to be set up
157+
* @param newPlatform a {@link MobileCapabilityType#PLATFORM_NAME} value which has
158+
* to be set up
163159
* @return {@link Capabilities} with changed mobile platform value
164160
*/
165161
protected static Capabilities substituteMobilePlatform(Capabilities originalCapabilities,
@@ -414,7 +410,7 @@ public void zoom(int x, int y) {
414410
@Override public DeviceRotation rotation() {
415411
Response response = execute(DriverCommand.GET_SCREEN_ROTATION);
416412
DeviceRotation deviceRotation =
417-
new DeviceRotation((Map<String, Number>) response.getValue());
413+
new DeviceRotation((Map<String, Number>) response.getValue());
418414
if (deviceRotation.getX() < 0 || deviceRotation.getY() < 0 || deviceRotation.getZ() < 0) {
419415
throw new WebDriverException("Unexpected orientation returned: " + deviceRotation);
420416
}
@@ -428,7 +424,7 @@ public void zoom(int x, int y) {
428424

429425
@Override public void rotate(ScreenOrientation orientation) {
430426
execute(DriverCommand.SET_SCREEN_ORIENTATION,
431-
ImmutableMap.of("orientation", orientation.value().toUpperCase()));
427+
ImmutableMap.of("orientation", orientation.value().toUpperCase()));
432428
}
433429

434430
@Override public ScreenOrientation getOrientation() {
@@ -464,12 +460,4 @@ private TouchAction createTap(int x, int y, int duration) {
464460
public URL getRemoteAddress() {
465461
return remoteAddress;
466462
}
467-
468-
/**
469-
* @return a map with values that hold session details.
470-
*/
471-
public Map<String, Object> getSessionDetails() {
472-
Response response = execute(GET_SESSION);
473-
return (Map<String, Object>) response.getValue();
474-
}
475463
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* See the NOTICE file distributed with this work for additional
5+
* information regarding copyright ownership.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.appium.java_client;
18+
19+
import static io.appium.java_client.MobileCommand.GET_SESSION;
20+
21+
import org.openqa.selenium.remote.Response;
22+
23+
import java.util.Map;
24+
25+
public interface HasSessionDetails extends ExecutesMethod {
26+
/**
27+
* @return a map with values that hold session details.
28+
*
29+
*/
30+
default Map<String, Object> getSessionDetails() {
31+
Response response = execute(GET_SESSION);
32+
return (Map<String, Object>) response.getValue();
33+
}
34+
35+
default Object getSessionDetail(String detail) {
36+
return getSessionDetails().get(detail);
37+
}
38+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
public interface MobileDriver<T extends WebElement> extends WebDriver, PerformsTouchActions, ContextAware, Rotatable,
3838
FindsByAccessibilityId<T>, LocationContext, DeviceActionShortcuts, TouchShortcuts,
3939
InteractsWithFiles, InteractsWithApps, HasAppStrings, FindsByClassName, FindsByCssSelector, FindsById,
40-
FindsByLinkText, FindsByName, FindsByTagName, FindsByXPath, FindsByFluentSelector<T>, ExecutesMethod {
40+
FindsByLinkText, FindsByName, FindsByTagName, FindsByXPath, FindsByFluentSelector<T>, ExecutesMethod,
41+
HasSessionDetails {
4142

4243
List<T> findElements(By by);
4344

src/main/java/io/appium/java_client/pagefactory/AndroidFindAll.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@
2626
* series of {@link io.appium.java_client.pagefactory.AndroidFindBy} tags
2727
* It will then search for all elements that match any criteria. Note that elements
2828
* are not guaranteed to be in document order.
29+
* It is deprecated. Set of {@link io.appium.java_client.pagefactory.AndroidFindBy}
30+
* can be defined without this annotation. To define the correct way how to use
31+
* the defined set please take a look at {@link HowToUseLocators}. The article.
32+
* https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html.
2933
*/
34+
@Deprecated
3035
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE})
3136
public @interface AndroidFindAll {
3237
/**

src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.appium.java_client.pagefactory;
1818

1919
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Repeatable;
2021
import java.lang.annotation.Retention;
2122
import java.lang.annotation.RetentionPolicy;
2223
import java.lang.annotation.Target;
@@ -30,6 +31,7 @@
3031
* using Android UI selectors, accessibility, id, name, class name, tag and xpath
3132
*/
3233
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE})
34+
@Repeatable(AndroidFindBySet.class)
3335
public @interface AndroidFindBy {
3436
/**
3537
* It is an is Android UIAutomator string.

0 commit comments

Comments
 (0)