Skip to content

Commit

Permalink
Repeatable annotations & HasSessionDetails API (#497)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
TikhomirovSergey authored Oct 31, 2016
1 parent 728d400 commit e2dafc7
Show file tree
Hide file tree
Showing 38 changed files with 645 additions and 255 deletions.
124 changes: 122 additions & 2 deletions docs/Page-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ List<RemoteWebElement> someElements;

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

- Chained
## - Chained

### If you use build versions < 5.x.x

```java
import org.openqa.selenium.remote.RemoteWebElement;
import io.appium.java_client.pagefactory.*;
Expand All @@ -98,7 +101,52 @@ RemoteWebElement someElement;
List<RemoteWebElement> someElements;
```

- Any possible
### If you use build versions >= 5.x.x

```java
import org.openqa.selenium.remote.RemoteWebElement;
import io.appium.java_client.pagefactory.*;
import org.openqa.selenium.support.FindBys;
import org.openqa.selenium.support.FindBy;

@FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
RemoteWebElement someElement;

@FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
List<RemoteWebElement> someElements;
```

or

```java
import org.openqa.selenium.remote.RemoteWebElement;
import io.appium.java_client.pagefactory.*;
import org.openqa.selenium.support.FindBys;
import org.openqa.selenium.support.FindBy;

import static io.appium.java_client.pagefactory.LocatorGroupStrategy.CHAIN;

@HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = CHAIN)
@FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
RemoteWebElement someElement;

@HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = CHAIN)
@FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
List<RemoteWebElement> someElements;
```

## - Any possible

### If you use build versions < 5.x.x

```java
import org.openqa.selenium.remote.RemoteWebElement;
import io.appium.java_client.pagefactory.*;
Expand All @@ -116,6 +164,78 @@ RemoteWebElement someElement;
List<RemoteWebElement> someElements;
```

### If you use build versions >= 5.x.x

```java
import org.openqa.selenium.remote.RemoteWebElement;
import io.appium.java_client.pagefactory.*;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.FindByAll;

import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE;

@HowToUseLocators(androidAutomation = ALL_POSSIBLE, iOSAutomation = ALL_POSSIBLE)
@FindAll{@FindBy(someStrategy1), @FindBy(someStrategy2)})
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
RemoteWebElement someElement;

@HowToUseLocators(androidAutomation = ALL_POSSIBLE, iOSAutomation = ALL_POSSIBLE)
@FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
List<RemoteWebElement> someElements;
```

## Also possible combined variants:

```java
import org.openqa.selenium.remote.RemoteWebElement;
import io.appium.java_client.pagefactory.*;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.FindByAll;

import static io.appium.java_client.pagefactory.LocatorGroupStrategy.CHAIN;
import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE;

@HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = ALL_POSSIBLE)
@FindAll{@FindBy(someStrategy1), @FindBy(someStrategy2)})
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
RemoteWebElement someElement;

@HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = ALL_POSSIBLE)
@FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
List<RemoteWebElement> someElements;
```

or

```java
import org.openqa.selenium.remote.RemoteWebElement;
import io.appium.java_client.pagefactory.*;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.FindByAll;

import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE;

@HowToUseLocators(iOSAutomation = ALL_POSSIBLE)
@FindAll{@FindBy(someStrategy1), @FindBy(someStrategy2)})
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2) //this is the chain
//by default
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
RemoteWebElement someElement;

@HowToUseLocators(iOSAutomation = ALL_POSSIBLE)
@FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
@AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2) //this is the chain
//by default
@iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
List<RemoteWebElement> someElements;
```

# Appium Java client is integrated with Selenium PageFactory by AppiumFieldDecorator.

Object fields are populated as below:
Expand Down
66 changes: 27 additions & 39 deletions src/main/java/io/appium/java_client/AppiumDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@
* limitations under the License.
*/


package io.appium.java_client;

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

import static io.appium.java_client.MobileCommand.GET_SESSION;

import com.google.common.collect.ImmutableMap;

import io.appium.java_client.remote.AppiumCommandExecutor;
Expand Down Expand Up @@ -58,18 +55,17 @@
import java.util.Map;
import java.util.Set;


/**
* @param <T> the required type of class which implement {@link org.openqa.selenium.WebElement}.
* Instances of the defined type will be returned via findElement* and findElements*
* Warning (!!!). Allowed types:
* {@link org.openqa.selenium.WebElement}
* {@link io.appium.java_client.TouchableElement}
* {@link org.openqa.selenium.remote.RemoteWebElement}
* {@link io.appium.java_client.MobileElement} and its subclasses that designed
* specifically
* for each target mobile OS (still Android and iOS)
*/
* @param <T> the required type of class which implement {@link org.openqa.selenium.WebElement}.
* Instances of the defined type will be returned via findElement* and findElements*
* Warning (!!!). Allowed types:
* {@link org.openqa.selenium.WebElement}
* {@link io.appium.java_client.TouchableElement}
* {@link org.openqa.selenium.remote.RemoteWebElement}
* {@link io.appium.java_client.MobileElement} and its subclasses that designed
* specifically
* for each target mobile OS (still Android and iOS)
*/
@SuppressWarnings("unchecked")
public abstract class AppiumDriver<T extends WebElement>
extends DefaultGenericMobileDriver<T> {
Expand All @@ -81,13 +77,13 @@ public abstract class AppiumDriver<T extends WebElement>
private ExecuteMethod executeMethod;

/**
* @param executor is an instance of {@link org.openqa.selenium.remote.HttpCommandExecutor}
* or class that extends it. Default commands or another vendor-specific
* commands may be specified there.
* @param capabilities take a look
* at {@link org.openqa.selenium.Capabilities}
* @param executor is an instance of {@link org.openqa.selenium.remote.HttpCommandExecutor}
* or class that extends it. Default commands or another vendor-specific
* commands may be specified there.
* @param capabilities take a look
* at {@link org.openqa.selenium.Capabilities}
* @param converterClazz is an instance of a class that extends
* {@link org.openqa.selenium.remote.internal.JsonToWebElementConverter}. It converts
* {@link org.openqa.selenium.remote.internal.JsonToWebElementConverter}. It converts
* JSON response to an instance of
* {@link org.openqa.selenium.WebElement}
*/
Expand All @@ -100,10 +96,10 @@ protected AppiumDriver(HttpCommandExecutor executor, Capabilities capabilities,
this.remoteAddress = executor.getAddressOfRemoteServer();
try {
Constructor<? extends JsonToWebElementConverter> constructor =
converterClazz.getConstructor(RemoteWebDriver.class);
converterClazz.getConstructor(RemoteWebDriver.class);
this.setElementConverter(constructor.newInstance(this));
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException
| InvocationTargetException e) {
| InvocationTargetException e) {
throw new RuntimeException(e);
}
}
Expand All @@ -116,7 +112,7 @@ public AppiumDriver(URL remoteAddress, Capabilities desiredCapabilities,

public AppiumDriver(URL remoteAddress, HttpClient.Factory httpClientFactory,
Capabilities desiredCapabilities,
Class<? extends JsonToWebElementConverter> converterClazz) {
Class<? extends JsonToWebElementConverter> converterClazz) {
this(new AppiumCommandExecutor(MobileCommand.commandRepository, remoteAddress,
httpClientFactory), desiredCapabilities, converterClazz);
}
Expand All @@ -129,7 +125,7 @@ public AppiumDriver(AppiumDriverLocalService service, Capabilities desiredCapabi

public AppiumDriver(AppiumDriverLocalService service, HttpClient.Factory httpClientFactory,
Capabilities desiredCapabilities,
Class<? extends JsonToWebElementConverter> converterClazz) {
Class<? extends JsonToWebElementConverter> converterClazz) {
this(new AppiumCommandExecutor(MobileCommand.commandRepository, service, httpClientFactory),
desiredCapabilities, converterClazz);
}
Expand All @@ -141,14 +137,14 @@ public AppiumDriver(AppiumServiceBuilder builder, Capabilities desiredCapabiliti

public AppiumDriver(AppiumServiceBuilder builder, HttpClient.Factory httpClientFactory,
Capabilities desiredCapabilities,
Class<? extends JsonToWebElementConverter> converterClazz) {
Class<? extends JsonToWebElementConverter> converterClazz) {
this(builder.build(), httpClientFactory, desiredCapabilities, converterClazz);
}

public AppiumDriver(HttpClient.Factory httpClientFactory, Capabilities desiredCapabilities,
Class<? extends JsonToWebElementConverter> converterClazz) {
this(AppiumDriverLocalService.buildDefaultService(), httpClientFactory, desiredCapabilities,
converterClazz);
this(AppiumDriverLocalService.buildDefaultService(), httpClientFactory,
desiredCapabilities, converterClazz);
}

public AppiumDriver(Capabilities desiredCapabilities,
Expand All @@ -158,8 +154,8 @@ public AppiumDriver(Capabilities desiredCapabilities,

/**
* @param originalCapabilities the given {@link Capabilities}.
* @param newPlatform a {@link MobileCapabilityType#PLATFORM_NAME} value which has
* to be set up
* @param newPlatform a {@link MobileCapabilityType#PLATFORM_NAME} value which has
* to be set up
* @return {@link Capabilities} with changed mobile platform value
*/
protected static Capabilities substituteMobilePlatform(Capabilities originalCapabilities,
Expand Down Expand Up @@ -414,7 +410,7 @@ public void zoom(int x, int y) {
@Override public DeviceRotation rotation() {
Response response = execute(DriverCommand.GET_SCREEN_ROTATION);
DeviceRotation deviceRotation =
new DeviceRotation((Map<String, Number>) response.getValue());
new DeviceRotation((Map<String, Number>) response.getValue());
if (deviceRotation.getX() < 0 || deviceRotation.getY() < 0 || deviceRotation.getZ() < 0) {
throw new WebDriverException("Unexpected orientation returned: " + deviceRotation);
}
Expand All @@ -428,7 +424,7 @@ public void zoom(int x, int y) {

@Override public void rotate(ScreenOrientation orientation) {
execute(DriverCommand.SET_SCREEN_ORIENTATION,
ImmutableMap.of("orientation", orientation.value().toUpperCase()));
ImmutableMap.of("orientation", orientation.value().toUpperCase()));
}

@Override public ScreenOrientation getOrientation() {
Expand Down Expand Up @@ -464,12 +460,4 @@ private TouchAction createTap(int x, int y, int duration) {
public URL getRemoteAddress() {
return remoteAddress;
}

/**
* @return a map with values that hold session details.
*/
public Map<String, Object> getSessionDetails() {
Response response = execute(GET_SESSION);
return (Map<String, Object>) response.getValue();
}
}
38 changes: 38 additions & 0 deletions src/main/java/io/appium/java_client/HasSessionDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.appium.java_client;

import static io.appium.java_client.MobileCommand.GET_SESSION;

import org.openqa.selenium.remote.Response;

import java.util.Map;

public interface HasSessionDetails extends ExecutesMethod {
/**
* @return a map with values that hold session details.
*
*/
default Map<String, Object> getSessionDetails() {
Response response = execute(GET_SESSION);
return (Map<String, Object>) response.getValue();
}

default Object getSessionDetail(String detail) {
return getSessionDetails().get(detail);
}
}
3 changes: 2 additions & 1 deletion src/main/java/io/appium/java_client/MobileDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
public interface MobileDriver<T extends WebElement> extends WebDriver, PerformsTouchActions, ContextAware, Rotatable,
FindsByAccessibilityId<T>, LocationContext, DeviceActionShortcuts, TouchShortcuts,
InteractsWithFiles, InteractsWithApps, HasAppStrings, FindsByClassName, FindsByCssSelector, FindsById,
FindsByLinkText, FindsByName, FindsByTagName, FindsByXPath, FindsByFluentSelector<T>, ExecutesMethod {
FindsByLinkText, FindsByName, FindsByTagName, FindsByXPath, FindsByFluentSelector<T>, ExecutesMethod,
HasSessionDetails {

List<T> findElements(By by);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@
* series of {@link io.appium.java_client.pagefactory.AndroidFindBy} tags
* It will then search for all elements that match any criteria. Note that elements
* are not guaranteed to be in document order.
* It is deprecated. Set of {@link io.appium.java_client.pagefactory.AndroidFindBy}
* can be defined without this annotation. To define the correct way how to use
* the defined set please take a look at {@link HowToUseLocators}. The article.
* https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html.
*/
@Deprecated
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE})
public @interface AndroidFindAll {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.appium.java_client.pagefactory;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand All @@ -30,6 +31,7 @@
* using Android UI selectors, accessibility, id, name, class name, tag and xpath
*/
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE})
@Repeatable(AndroidFindBySet.class)
public @interface AndroidFindBy {
/**
* It is an is Android UIAutomator string.
Expand Down
Loading

0 comments on commit e2dafc7

Please sign in to comment.