您的位置:首页 > 移动开发 > Objective-C

appium():PageObject&PageFactory

2016-11-24 16:12 375 查看
Appium Java client has facilities which components to Page Object design pattern and Selenium PageFactory.//appium的java客户端支持PageObject和PageFactory。读本文之前一定要有PageObject和PageFactory的概念。

原文地址:https://github.com/appium/java-client/blob/master/docs/Page-objects.md#specification

//第一部分:讲解声明filed时,如何使用注解

WebElement/list of WebElement field can be populated by default:

//对于WebElement使用@Findby注解,元素类型是WebElement。


1 import org.openqa.selenium.WebElement;
2 import org.openqa.selenium.support.FindBy;
3 ...
4
5 @FindBy(someStrategy) //for browser or web view html UI
6 //also for mobile native applications when other locator strategies are not defined
7 WebElement someElement;
8
9 @FindBy(someStrategy) //for browser or web view html UI
10 //also for mobile native applications when other locator strategies are not defined
11 List<WebElement> someElements;




If there is need to use convinient locators for mobile native applications then the following is available:

//对于移动端原生应用,分别使用@AndroidFindBy@SelendroidFindBy@iOSFindBy注解,元素类型分别为AndroidElement、RemoteWebElement、IOSElement。

1 import io.appium.java_client.android.AndroidElement;
2 import org.openqa.selenium.remote.RemoteWebElement;
3 import io.appium.java_client.pagefactory.*;
4 import io.appium.java_client.ios.IOSElement;
5
6 @AndroidFindBy(someStrategy) //for Android UI when Android UI automator is used
7 AndroidElement someElement;
8
9 @AndroidFindBy(someStrategy) //for Android UI when Android UI automator is used
10 List<AndroidElement> someElements;
11
12 @SelendroidFindBy(someStrategy) //for Android UI when Selendroid automation is used
13 RemoteWebElement someElement;
14
15 @SelendroidFindBy(someStrategy) //for Android UI when Selendroid automation is used
16 List<RemoteWebElement> someElements;
17
18 @iOSFindBy(someStrategy) //for iOS native UI
19 IOSElement someElement;
20
21 @iOSFindBy(someStrategy) //for iOS native UI
22 List<IOSElement> someElements;


The example for the crossplatform mobile native testing

//跨平台时,同时使用@AndroidFindBy@iOSFindBy注解,元素类型为MobileElement。

1 import io.appium.java_client.MobileElement;
2 import io.appium.java_client.pagefactory.*;
3
4 @AndroidFindBy(someStrategy)
5 @iOSFindBy(someStrategy)
6 MobileElement someElement;
7
8 @AndroidFindBy(someStrategy) //for the crossplatform mobile native
9 @iOSFindBy(someStrategy) //testing
10 List<MobileElement> someElements;


The fully cross platform examle

//全平台时,同时使用@FindBy@AndroidFindBy@iOSFindBy注解,元素类型为RemoteWebElement。

1 import org.openqa.selenium.remote.RemoteWebElement;
2 import io.appium.java_client.pagefactory.*;
3 import org.openqa.selenium.support.FindBy;
4
5 //the fully cross platform examle
6 @FindBy(someStrategy) //for browser or web view html UI
7 @AndroidFindBy(someStrategy) //for Android native UI
8 @iOSFindBy(someStrategy)  //for iOS native UI
9 RemoteWebElement someElement;
10
11 //the fully cross platform examle
12 @FindBy(someStrategy)
13 @AndroidFindBy(someStrategy) //for Android native UI
14 @iOSFindBy(someStrategy)  //for iOS native UI
15 List<RemoteWebElement> someElements;


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

//chained是指使用@FindBys、@AndroidFindBys、@iOSFindBys注解。修饰列表变量时,列表的内容是多个定位方法找到的元素。修饰非列表变量时,变量值是什么呢?

- Chained

If you use build versions < 5.x.x

1 import org.openqa.selenium.remote.RemoteWebElement;
2 import io.appium.java_client.pagefactory.*;
3 import org.openqa.selenium.support.FindBys;
4 import org.openqa.selenium.support.FindBy;
5
6 @FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
7 @AndroidFindBys({@AndroidFindBy(someStrategy1), @AndroidFindBy(someStrategy2)})
8 @iOSFindBys({@iOSFindBy(someStrategy1), @iOSFindBy(someStrategy2)})
9 RemoteWebElement someElement;
10
11 @FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
12 @AndroidFindBys({@AndroidFindBy(someStrategy1), @AndroidFindBy(someStrategy2)})
13 @iOSFindBys({@iOSFindBy(someStrategy1), @iOSFindBy(someStrategy2)})
14 List<RemoteWebElement> someElements;


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

1 import org.openqa.selenium.remote.RemoteWebElement;
2 import io.appium.java_client.pagefactory.*;
3 import org.openqa.selenium.support.FindBys;
4 import org.openqa.selenium.support.FindBy;
5
6 @FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
7 @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
8 @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
9 RemoteWebElement someElement;
10
11 @FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
12 @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
13 @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
14 List<RemoteWebElement> someElements;


or

1 import org.openqa.selenium.remote.RemoteWebElement;
2 import io.appium.java_client.pagefactory.*;
3 import org.openqa.selenium.support.FindBys;
4 import org.openqa.selenium.support.FindBy;
5
6 import static io.appium.java_client.pagefactory.LocatorGroupStrategy.CHAIN;
7
8 @HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = CHAIN)
9 @FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
10 @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
11 @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
12 RemoteWebElement someElement;
13
14 @HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = CHAIN)
15 @FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)})
16 @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
17 @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
18 List<RemoteWebElement> someElements;


//possible是指使用@FindAll@AndroidFindAll@iOSFindAll注解。如果修饰列表变量,那么列表的内容是由全部声明的定位方法找到的元素。如果修饰非列表变量,那么变量值是第一个被找到的元素,无论是使用哪个已声明的定位方法。

- Any possible

If you use build versions < 5.x.x

1 import org.openqa.selenium.remote.RemoteWebElement;
2 import io.appium.java_client.pagefactory.*;
3 import org.openqa.selenium.support.FindBy;
4 import org.openqa.selenium.support.FindByAll;
5
6 @FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
7 @AndroidFindAll({@AndroidFindBy(someStrategy1), @AndroidFindBy(someStrategy2)})
8 @iOSFindAll({@iOSFindBy(someStrategy1), @iOSFindBy(someStrategy2)})
9 RemoteWebElement someElement;
10
11 @FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
12 @AndroidFindAll({@AndroidFindBy(someStrategy1), @AndroidFindBy(someStrategy2)})
13 @iOSFindAll({@iOSFindBy(someStrategy1), @iOSFindBy(someStrategy2)})
14 List<RemoteWebElement> someElements;


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

1 import org.openqa.selenium.remote.RemoteWebElement;
2 import io.appium.java_client.pagefactory.*;
3 import org.openqa.selenium.support.FindBy;
4 import org.openqa.selenium.support.FindByAll;
5
6 import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE;
7
8 @HowToUseLocators(androidAutomation = ALL_POSSIBLE, iOSAutomation = ALL_POSSIBLE)
9 @FindAll{@FindBy(someStrategy1), @FindBy(someStrategy2)})
10 @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
11 @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
12 RemoteWebElement someElement;
13
14 @HowToUseLocators(androidAutomation = ALL_POSSIBLE, iOSAutomation = ALL_POSSIBLE)
15 @FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
16 @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
17 @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
18 List<RemoteWebElement> someElements;


Also possible combined variants:

1 import org.openqa.selenium.remote.RemoteWebElement;
2 import io.appium.java_client.pagefactory.*;
3 import org.openqa.selenium.support.FindBy;
4 import org.openqa.selenium.support.FindByAll;
5
6 import static io.appium.java_client.pagefactory.LocatorGroupStrategy.CHAIN;
7 import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE;
8
9 @HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = ALL_POSSIBLE)
10 @FindAll{@FindBy(someStrategy1), @FindBy(someStrategy2)})
11 @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
12 @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
13 RemoteWebElement someElement;
14
15 @HowToUseLocators(androidAutomation = CHAIN, iOSAutomation = ALL_POSSIBLE)
16 @FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
17 @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2)
18 @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
19 List<RemoteWebElement> someElements;


or

1 import org.openqa.selenium.remote.RemoteWebElement;
2 import io.appium.java_client.pagefactory.*;
3 import org.openqa.selenium.support.FindBy;
4 import org.openqa.selenium.support.FindByAll;
5
6 import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE;
7
8 @HowToUseLocators(iOSAutomation = ALL_POSSIBLE)
9 @FindAll{@FindBy(someStrategy1), @FindBy(someStrategy2)})
10 @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2) //this is the chain
11 //by default
12 @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
13 RemoteWebElement someElement;
14
15 @HowToUseLocators(iOSAutomation = ALL_POSSIBLE)
16 @FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)})
17 @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2) //this is the chain
18 //by default
19 @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2)
20 List<RemoteWebElement> someElements;


//第二部分:讲解如何使用PageFactory初始化field。

Appium Java client is integrated with Selenium PageFactory by AppiumFieldDecorator.

Object fields are populated as below://按照这个方式初始化。

1 import io.appium.java_client.pagefactory.*;
2 import org.openqa.selenium.support.PageFactory;
3
4 PageFactory.initElements(new AppiumFieldDecorator(searchContext
5               /*searchContext is a WebDriver or WebElement
6               instance */),
7               pageObject //an instance of PageObject.class
8 );


//初始化时,可以设置元素定位的超时时间。

1 import io.appium.java_client.pagefactory.*;
2 import org.openqa.selenium.support.PageFactory;
3 import java.util.concurrent.TimeUnit;
4
5 PageFactory.initElements(new AppiumFieldDecorator(searchContext,
6               /*searchContext is a WebDriver or WebElement
7               instance */
8         15, //default implicit waiting timeout for all strategies//效果为:至少用15个时间单位查找元素。
9         TimeUnit.SECONDS),
10             pageObject //an instance of PageObject.class
11 );
12 import io.appium.java_client.pagefactory.*;
13 import org.openqa.selenium.support.PageFactory;
14 import java.util.concurrent.TimeUnit;
15
16 PageFactory.initElements(new AppiumFieldDecorator(searchContext,
17               /*searchContext is a WebDriver or WebElement
18               instance */
19         new TimeOutDuration(15, //default implicit waiting timeout for all strategies
20         TimeUnit.SECONDS)), //效果为:至少用15个时间单位查找元素。


21 pageObject //an instance of PageObject.class 22 );


If time of the waiting for elements differs from usual (longer, or shorter when element is needed only for quick checkings/assertions) then//还可以为每个元素单独设置超时时间。

1 import io.appium.java_client.pagefactory.*;
2
3 @WithTimeout(timeOut = yourTime, timeUnit = yourTimeUnit)
4 RemoteWebElement someElement;
5
6 @WithTimeout(timeOut = yourTime, timeUnit = yourTimeUnit)
7 List<RemoteWebElement> someElements;


//第三部分:讲解一个例子。

The additional feature.

The simple example

Let's imagine that the task is to check an Android client of the http://www.rottentomatoes.com. Let it be like a picture below





Lets imagine that it is only a part of the screen.

A typical page object could look like://典型的page object的写法

1 public class RottenTomatoesScreen {
2     //convinient locator
3     private List<AndroidElement> titles;
4
5     //convinient locator
6     private List<AndroidElement> scores;
7
8     //convinient locator
9     private List<AndroidElement> castings;
10     //element declaration goes on
11
12     public String getMovieCount(){
13        //.......
14     }
15
16     public String getTitle(params){
17        //.......
18     }
19
20     public String getScore(params){
21       //.......
22     }
23
24     public String getCasting(params){
25       //.......
26     }
27
28     public void openMovieInfo(params){
29       //.......
30     }
31
32     //method declaration goes on
33 }


The description above can be decomposed. Let's work it out!//上面的PageObject写法,可以进行拆解。

Firstly a Movie-widget could be described this way://首先,定义一个widget:猜测是页面上的一个对象,假设烂番茄网站上也有电视剧、动画片的信息,而这两者又有不同的介绍,比如电视剧有演员信息,动画片有配音人员和设计师的信息,那么电视剧和动画可以看做是两个不同的widget。

1 import io.appium.java_client.pagefactory.Widget;
2 import org.openqa.selenium.WebElement;
3
4 public class Movie extends Widget{
5    protected Movie(WebElement element) {
6         super(element);
7    }
8
9    //convinient locator  //大多时候,不同movie的具有相同的id或者classname,这时候用id或者classname就可以定位全部的movie。同理,不同movie的title也都有相同的id或者classname,一个定位字符串可以通用与全部的title。
10    private AndroidElement title;
11
12    //convinient locator
13    private AndroidElement score;
14
15    //convinient locator
16    private AndroidElement casting;
17
18    public String getTitle(params){
19        //.......
20    }
21
22    public String getScore(params){
23       //.......
24    }
25
26    public String getCasting(params){
27       //.......
28    }
29
30    public void openMovieInfo(params){
31        ((AndroidElement) getWrappedElement()).tap(1, 1500);
32    }
33
34 }


So, now page object looks

1 public class RottenTomatoesScreen {
2
3      @AndroidFindBy(a locator which convinient to find a single movie-root - element)//不同movie具有相同的id或者classname,这时候一个定位字符串可以通用于全部的movie。
4      private List<Movie> movies;
5
6       //element declaration goes on
7
8      public String getMovieCount(){
9         return movies.size();
10      }
11
12      public Movie getMovie(int index){
13         //any interaction with sub-elements of a movie-element
14         //will be performed outside of the page-object instance
15         return movie.get(index);
16      }
17      //method declaration goes on
18 }


Ok. What if Movie-class is reused and a wrapped root element is usually found by the same locator?

Then//如果需要使用movie类生成多个实例,而且这些实例使用同一个定位字符串,那么可以将实例的注解移到定义movie类的代码前面。

1 //the class is annotated !!!
2 @AndroidFindBy(a locator which convinient to find a single movie-root - element)
3 public class Movie extends Widget{
4 ...
5 }


and

1 public class RottenTomatoesScreen {
2      //!!! locator is not necessary at this case
3      private List<Movie> movies;
4 ...
5 }


Ok. What if movie list is not a whole screen? E.g. we want to describe it as a widget with nested movies.

Then://这里是说,如何生成一个特殊的widget,它的内部元素还是widget。假设当前页面是一个内容非常丰富的页面,有电影、电视剧、综艺节目相关的信息,电影分类内部的每个电影都有名字、得分、剧情简介等信息,电视剧和综艺节目也有各自的内部信息。此时,每部电影都是一个小的widget,电影类别则是一个大的widget,整个页面由电影、电视剧、综艺节目三个大的widget组成。注解的使用方法同前面一样。

1 //with the usual locator or without it
2 public class Movies extends Widget{
3
4     //with a custom locator or without it
5     private List<Movie> movies;
6 ...
7 }


and

1 public class RottenTomatoesScreen {
2
3     //with a custom locator or without it
4     Movies movies;
5 ...
6 }


Good! How to poputate all these fields?

As usual:

1 RottenTomatoesScreen screen = new RottenTomatoesScreen();
2 PageFactory.initElements(new AppiumFieldDecorator(searchContext /*WebDriver or WebElement
3               instance */), screen);


//第四部分:说明

Specification

A class which describes a widget or group of elements should extend//要想使用widget,必须要继承widget类。

io.appium.java_client.pagefactory.Widget;


Any widget/group of elements can be described it terms of sub-elements or nested sub-widgets.
Appium-specific annotations are used for this purpose.

Any class which describes the real widget or group of elements can be annotated

That means that when the same "widget" is used frequently and any root element of this can be found by the same locator then user can

1 @FindBy(relevant locator) //how to find a root element//假设UsersWidget对应于实际页面的movie,那么这个定位字符串是能够匹配全部movie的通用的字符串。
2 public class UsersWidget extends Widget{
3
4   @FindBy(relevant locator) //this element will be found //同理,这里的定位字符串也必须是通用的匹配全部subElement1的字符串。
5   //using the root element
6   WebElement subElement1;
7
8   @FindBy(relevant locator) //this element will be found
9   //using the root element
10   WebElement subElement2;
11
12   @FindBy(relevant locator) //a root element
13   //of this widget is the sub-element which
14   //will be found from top-element
15   UsersWidget subWidget;
16
17   //and so on..
18 }


and then it is enough

1   //above is the other field declaration
2
3   UsersWidget widget;
4
5   //below is the other field/method declaration


If the widget really should be found using an another locator then

1   //above is the other field declaration
2   @FindBy(another relevant locator) //this locator overrides //这里的定位字符串可以覆盖声明类时使用的定位字符串。
3   //the declared in the using class
4   UsersWidget widget;
5
6   //below is the other field/method declaration


Ok. What should users do if they want to implement a subclass which describes a similar group of elements for the same platform?

There is nothing special.//子类继承父类没有特殊的操作。

1 @FindBy(relevant locator) //how to find a root element
2 public class UsersWidget extends Widget{
3 ...
4 }
5 //at this case the root element will be found by the locator
6 //which is declared in superclass
7 public class UsersOverriddenWidget extends UsersWidget {
8 ...
9 }


and

1 @FindBy(relevant locator2) //this locator overrides
2 //all locators declared in superclasses
3 public class UsersOverriddenWidget2 extends UsersWidget {
4 ...
5 }


Is it possible to reuse "widgets" in crossplatform testing?

If there is no special details of interaction with an application browser version and/or versions for different mobile OS's then

1 @FindBy(relevant locator for browser/webview html or by default)
2 @AndroidFindBy(relevant locator for Android UI automator)
3 @iOSFindBy(relevant locator for iOS UI automation)
4 public class UsersWidget extends Widget {
5
6   @FindBy(relevant locator for browser/webview html or by default)
7   @AndroidFindBy(relevant locator for Android UI automator)
8   @iOSFindBy(relevant locator for iOS UI automation)
9   RemoteWebElement subElement1;
10
11   @FindBy(relevant locator for browser/webview html or by default)
12   @AndroidFindBy(relevant locator for Android UI automator)
13   @iOSFindBy(relevant locator for iOS UI automation)
14   RemoteWebElement subElement2;
15
16   //overrides a html/default
17   //locator declared in the used class
18   @FindBy(relevant locator for browser/webview html or by default)
19   //overrides an Android UI automator
20   //locator declared in the used class
21   @AndroidFindBy(relevant locator for Android UI automator)
22   //overrides an iOS UI automation
23   //locator declared in the using class
24   @iOSFindBy(relevant locator for iOS UI automation)
25   UsersWidget subWidget;
26
27   //and so on..
28 }


What if interaction with a "widget" has special details for each used platform, but the same at high-level

Then it is possible

1 public /*abstract*/ class DefaultAbstractUsersWidget extends Widget{
2
3 }


and

1 @FindBy(locator)
2 public class UsersWidgetForHtml extends DefaultAbstractUsersWidget {
3
4 }


and

1 @AndroidFindBy(locator)
2 public class UsersWidgetForAndroid extends DefaultAbstractUsersWidget {
3
4 }


and even

1 @iOSFindBy(locator)
2 public class UsersWidgetForIOS extends DefaultAbstractUsersWidget {
3
4 }


and then

1   import io.appium.java_client.pagefactory.OverrideWidget;
2   ...
3
4   //above is the other field declaration
5   @OverrideWidget(html = UsersWidgetForHtml.class,
6   androidUIAutomator = UsersWidgetForAndroid.class,
7   iOSUIAutomation = UsersWidgetForIOS .class)
8   DefaultAbstractUsersWidget widget;
9
10   //below is the other field/method declaration


This use case has some restrictions;

All classes which are declared by the OverrideWidget annotation should be subclasses of the class declared by field

All classes which are declared by the OverrideWidget should not be abstract. If a declared class is overriden partially like

1   //above is the other field declaration
2
3   @OverrideWidget(iOSUIAutomation = UsersWidgetForIOS .class)
4   DefaultUsersWidget widget; //lets assume that there are differences of
5   //interaction with iOS and by default we use DefaultUsersWidget.
6   //Then DefaultUsersWidget should not be abstract too.
7   //
8
9   //below is the other field/method declaration


for now it is not possible to

1   import io.appium.java_client.pagefactory.OverrideWidget;
2   ...
3
4   //above is the other field declaration
5   @OverrideWidget(html = UsersWidgetForHtml.class,
6   androidUIAutomator = UsersWidgetForAndroid.class,
7   iOSUIAutomation = UsersWidgetForIOS .class)
8   DefaultAbstractUsersWidget widget;
9
10   //below is the other field/method declaration
11
12 //user's code
13 ((UsersWidgetForAndroid) widget).doSpecialWorkForAndroing()


The workaround:

1   import io.appium.java_client.pagefactory.OverrideWidget;
2   ...
3
4   //above is the other field declaration
5   @OverrideWidget(html = UsersWidgetForHtml.class,
6   androidUIAutomator = UsersWidgetForAndroid.class,
7   iOSUIAutomation = UsersWidgetForIOS .class)
8   DefaultAbstractUsersWidget widget;
9
10   //below is the other field/method declaration
11
12 //user's code
13 ((UsersWidgetForAndroid) widget.getSelfReference()).doSpecialWorkForAndroing()


Good! What about widget lists?

All that has been mentioned above is true for "widget" lists.

One more restriction

It is strongly recommended to implement each subclass of io.appium.java_client.pagefactory.Widget with this constructor

1    public /*or any other available modifier*/ WidgetSubclass(WebElement element) {
2        super(element);
3    }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐