原文地址http://blog.csdn.net/TalorSwfit20111208/article/details/77434950
由於無法聯系上您,在此分享您的文章,希望諒解!
Appium PageObject 直接沿用了Selenium的PageObject設計模式,
PageObject主要優點如下:
一、將UI元素與邏輯分離方便后期維護
二、減少代碼冗余
三、增強代碼可讀性
來看個例子
沒有使用PO設計模式的代碼如下:
- @Test
- public void twoPlusTwoOperation() {
- /* 獲取控件*/
- MobileElement buttonTwo = (MobileElement)(driver.findElement(By.id("net.ludeke.calculator:id/digit2")));
- MobileElement buttonPlus = (MobileElement)(driver.findElement(By.id("net.ludeke.calculator:id/plus")));
- MobileElement buttonEquals = (MobileElement)(driver.findElement(By.id("net.ludeke.calculator:id/equal")));
- MobileElement resultField = (MobileElement)(driver.findElement(By.xpath("//android.widget.EditText[1]")));
- /* 計算2+2*/
- buttonTwo.click();
- buttonPlus.click();
- buttonTwo.click();
- buttonEquals.click();
- /* 檢查在給定的時間內是否顯示要查找的控件 */
- new WebDriverWait(driver, 30).until(ExpectedConditions.textToBePresentInElement(resultField, EXPECTED_RESULT_FOUR));
- }
使用了PO設計模式的代碼如下
- @Test
- public void twoPlusTwoOperation() {
- app.calculatorScreen().addTwoAndTwo();
- assertTrue(app.calculatorScreen().isResultCorrect("4"));
- }
我們注意到的第一個最直接的變化是測試方法的長度。使用PageObject模式編寫的測試方法幾乎總是比原始的方法短(對於較長的測試而言更短)。如果你繼續閱讀,你會注意到,這不僅是因為我們在addTwoAndTwo方法中包裝了所有的按鈕。
可讀性怎么樣?再次通過這兩種方法,問問自己在哪種情況下更容易理解發生了什么。另外,請注意我們如何在第二種方法中真的不需要注釋,因為指定與Page Object具有的交互的方法具有重要的名稱。
通過將低級操作包含在專用方法中,我們現在有了不直接引用任何WebDriver API的測試方法。在編寫第一個PageObject測試方法時,請使用缺少引用低級API的導入語句作為根據模式進行處理的指標。
這種方法給了我們另一個不容忽視的優點:通過隱藏單一實用程序方法的技術復雜性,PageObject模式使得用戶交互的流程變得明顯。對於更長,更復雜的測試,以及我們編寫測試的整個方式的轉換,這特別有用。一旦實現了應用程序屏幕的基本交互,編寫測試方法基本上只是通過調用正確名稱所指的方法來復制用例。這就是為什么你應該努力為他們選擇最好的名字。
PageObject控件定位
pageobject控件定位是使用注解方式來定位的,如下
# WebElement/列表 WebElement 字段可以這樣定位:
使用@FindBy注解
- import org.openqa.selenium.support.FindBy;
- import org.openqa.selenium.WebElement;
- @FindBy(someStrategy)//用來定位瀏覽器或者webview UI
- //也可以用來定位native 應用,當沒有定義其他定位策略時
- WebElement someElement;@FindBy(someStrategy) //用來定位瀏覽器或者webview UI
- //也可以用來定位native 應用,當沒有定義其他定位策略時
- List<WebElement> someElements;//定位包含相同控件屬性的控件
使用@AndroidFindBy來定位
- import io.appium.java_client.android.AndroidElement;
- import org.openqa.selenium.remote.RemoteWebElement;
- import io.appium.java_client.pagefactory.*;
- @AndroidFindBy(someStrategy) //用Android UI Automator來定位Android UI
- AndroidElement someElement;
- @AndroidFindBy(someStrategy) //用Android UI Automator來定位Android UI
- List<AndroidElement> someElements;
多種組合查找策略
- 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;
## 也可以用下面這種方式:
- 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;
或者
- 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;
應用PO查找
# Appium Java client使用了AppiumFieldDecorator來融合了Selenium PageFactory
對象字段結構如下:
- import io.appium.java_client.pagefactory.*;
- import org.openqa.selenium.support.PageFactory;
- PageFactory.initElements(new AppiumFieldDecorator(searchContext
- /*searchContext is a WebDriver or WebElement
- instance */),
- pageObject //對象類的一個實例
- );
- import io.appium.java_client.pagefactory.*;
- import org.openqa.selenium.support.PageFactory;
- import java.util.concurrent.TimeUnit;
- PageFactory.initElements(new AppiumFieldDecorator(searchContext,
- /*searchContext is a WebDriver or WebElement
- instance */
- 15, //默認為所有查找策略的隱式等待時間
- TimeUnit.SECONDS),
- pageObject //對象類的一個實例
- );
- import io.appium.java_client.pagefactory.*;
- import org.openqa.selenium.support.PageFactory;
- import java.util.concurrent.TimeUnit;
- PageFactory.initElements(new AppiumFieldDecorator(searchContext,
- /*searchContext is a WebDriver or WebElement
- instance */
- new TimeOutDuration(15, //默認為所有查找策略的隱式等待時間
- TimeUnit.SECONDS)),
- pageObject //對象類的一個實例
- );
來看個經典計算器程序在PageObject中的實際應用
項目結構
安裝程序的核心組件將是以下類:
- AbstractTest:我們在其中設置測試階段;
- AppiumDriverBuilder:設置所需的功能並實例化驅動程序;
- 我們調用的應用程序類訪問我們的Screen對象;
- AbstractScreen,包含您的Screen對象之間的所有共享方法;
- Screen類包含表示用戶與被測試應用程序交互的方法;
- 測試類包含一個或多個測試,寫成屏幕方法調用序列。
為了進一步澄清項目結構,您可以將這些類組織成包。在一個實用程序包中,您可以包括您的AppiumDriverBuilder類以及您創建的其他實用程序類。您也可以將AbstractScreen類和其他Screen類放在屏幕包中。項目結構如下:
AbstractTest
在項目的中心是AbstractTest類。這里我們定義我們的測試套件方法,它將在每次測試運行之前執行。這里我們做兩件非常重要的事情:
- 負責初始化負責連接到Appium服務器的驅動程序;
- 實例化了App類,這將允許我們訪問我們想要測試的應用程序的單個屏幕;
- package com.test.calculatortest;
- import com.test.calculatortest.Calculator;
- import com.test.calculatortest.util.AppiumDriverBuilder;
- import io.appium.java_client.AppiumDriver;
- import org.junit.Before;
- import java.net.MalformedURLException;
- import java.net.URL;
- public abstract class AbstractTest {
- private AppiumDriver<?> driver;
- protected Calculator app;
- /* Establish a connection to TestObject, or to a local device test is local. */
- @Before
- public void connect() throws MalformedURLException {
- this.driver = AppiumDriverBuilder.forAndroid()
- .withEndpoint(new URL("http://127.0.0.1:4723/wd/hub"))
- .build("com.android.calculator2", ".Calculator");
- //實例化應用類
- app = new Calculator(driver);
- }
- }
AppiumDriverBuilder
它基本上是一個支持類,負責配置和實例化的Appium驅動程序。
- package com.test.calculatortest.util;
- import io.appium.java_client.AppiumDriver;
- import io.appium.java_client.android.AndroidDriver;
- import io.appium.java_client.android.AndroidElement;
- import org.openqa.selenium.remote.DesiredCapabilities;
- import java.net.URL;
- public abstract class AppiumDriverBuilder<SELF, DRIVER extends AppiumDriver<?>> {
- public static AndroidDriverBuilder forAndroid() {
- return new AndroidDriverBuilder();
- }
- public static class AndroidDriverBuilder extends AppiumDriverBuilder<AndroidDriverBuilder, AndroidDriver<?>> {
- DesiredCapabilities capabilities = new DesiredCapabilities();
- @Override
- public AndroidDriver<?> build(String appPackage,String appActivity) {
- capabilities.setCapability("platformName", "Android");
- //使用Android模擬器
- capabilities.setCapability("deviceName", "testDevice");
- //使用Android模擬器
- capabilities.setCapability("platformVersion", "4.4.4");
- //不重新安裝應用
- capabilities.setCapability("noReset",true);
- //待測包名及首次啟動的頁面
- capabilities.setCapability("appPackage", appPackage);
- capabilities.setCapability("appActivity", appActivity);
- //使用appium Unicode鍵盤輸入法,輸入完畢后重置輸入法
- capabilities.setCapability("unicodeKeyboard", true);
- capabilities.setCapability("resetKeyboard", true);
- capabilities.setCapability("deviceReadyTimeout",30);
- return new AndroidDriver<AndroidElement>(endpoint, capabilities);
- }
- }
- protected URL endpoint;
- @SuppressWarnings("unchecked")
- public SELF withEndpoint(URL endpoint) {
- this.endpoint = endpoint;
- return (SELF) this;
- }
- public abstract DRIVER build(String appPackage,String appActivity);
- }
應用類
測試中的另一個中心類將是Application類(我們簡單的命名為我們正在測試的應用程序的名稱)。這個類的功能是提供屏幕(正如我們之前所說的,Page對象)到需要訪問它們的功能的方法(屏幕類中的測試方法)。
- package com.test.calculatortest;
- import com.test.calculatortest.screen.CalculatorScreen;
- import io.appium.java_client.AppiumDriver;
- /*
- * 應用類,返回各個操作頁面類
- */
- public class Calculator {
- private final AppiumDriver<?> driver;
- public Calculator(AppiumDriver<?> driver) {
- this.driver = driver;
- }
- public CalculatorScreen calculatorScreen() {
- return new CalculatorScreen(driver);
- }
- }
AbstractScreen類
AbstractScreen類將包含Screen對象之間共享的所有方法。這些可能是通用目的的方法,可以執行多個點(滑動,滾動)與應用程序交互所需的手勢,這些手段隱藏了一些更為復雜的代碼,從而增加了測試方法的可讀性,同步方法等。
- package com.test.calculatortest.screen;
- import io.appium.java_client.AppiumDriver;
- import io.appium.java_client.MobileElement;
- import io.appium.java_client.pagefactory.AppiumFieldDecorator;
- import org.openqa.selenium.By;
- import org.openqa.selenium.OutputType;
- import org.openqa.selenium.support.PageFactory;
- import org.openqa.selenium.support.ui.ExpectedConditions;
- import org.openqa.selenium.support.ui.WebDriverWait;
- public abstract class AbstractScreen {
- protected final AppiumDriver<?> driver;
- public AbstractScreen(AppiumDriver<?> driver) {
- this.driver = driver;
- PageFactory.initElements(new AppiumFieldDecorator(driver), this);
- }
- public MobileElement findElementWithTimeout(By by, int timeOutInSeconds) {
- return (MobileElement)(new WebDriverWait(driver, timeOutInSeconds)).until(ExpectedConditions.presenceOfElementLocated(by));
- }
- protected void takeScreenShot(){
- driver.getScreenshotAs(OutputType.BASE64);
- }
- }
注意
PageFactory.initElements(new AppiumFieldDecorator(driver), this);這句,這可以讓你使用注釋來抓取UI元素,因此請勿忘記將其包含在您的設置中!
屏幕類
屏幕類代表應用程序的屏幕。在這里獲取UI元素並與代表可能與用戶界面交互的方法與其進行交互,例如打開菜單並選擇項目,填寫某些字段並按下提交按鈕,向下滾動列表並選擇正確的元素這樣,你的測試方法將只是不同屏幕上的一系列用戶交互。這將使你的測試易於維護和擴展。
- package com.test.calculatortest.screen;
- import io.appium.java_client.AppiumDriver;
- import io.appium.java_client.MobileElement;
- import io.appium.java_client.pagefactory.AndroidFindBy;
- import org.openqa.selenium.TimeoutException;
- import org.openqa.selenium.support.ui.ExpectedConditions;
- import org.openqa.selenium.support.ui.WebDriverWait;
- public class CalculatorScreen extends AbstractScreen {
- @AndroidFindBy(id = "com.android.calculator2:id/digit2")
- private MobileElement buttonTwo;
- @AndroidFindBy(id = "com.android.calculator2:id/plus")
- private MobileElement buttonPlus;
- @AndroidFindBy(id = "com.android.calculator2:id/equal")
- private MobileElement buttonEquals;
- @AndroidFindBy(xpath = "//android.widget.EditText[1]")
- private MobileElement resultField;
- public CalculatorScreen(AppiumDriver<?> driver) {
- super(driver);
- }
- public void addTwoAndTwo() {
- buttonTwo.click();
- buttonPlus.click();
- buttonTwo.click();
- buttonEquals.click();
- }
- public boolean isResultCorrect(String result) {
- try {
- /* Check if within given time the correct result appears in the designated field. */
- (new WebDriverWait(driver, 30)).until(ExpectedConditions.textToBePresentInElement(resultField, result));
- return true;
- } catch (TimeoutException e) {
- return false;
- }
- }
- }
除了剛剛描述的應用程序的顯着“主屏幕”之外,我們還可以創建另一個表示計算器應用程序的“高級面板”的程序,這基本上是自己的屏幕。在此屏幕中引用的UI元素將是計算器的符號/函數。
你可以為應用程序的每個屏幕創建一個Screen對象,也可以決定僅對真正重要的屏幕執行此操作。這兩種方法都有其優點和缺點,但請記住,可以使用太多屏幕的應用程序來處理,另一方面,在一個Screen對象中抽取太多的屏幕可能會導致混亂。
測試類
您的測試按照擴展AbstractTest的類進行分組。這允許您抓住應用程序的任何屏幕,並通過您編寫的方法與其進行交互。
- package com.test.calculatortest;
- import org.junit.Test;
- import static org.junit.Assert.assertTrue;
- /*
- *
- * 邏輯操作類
- *
- */
- public class OperationTests extends AbstractTest {
- public OperationTests() {}
- /* 一個簡單的加法運算,期望結果為正確的值 */
- @Test
- public void twoPlusTwoOperation() {
- app.calculatorScreen().addTwoAndTwo();
- assertTrue(app.calculatorScreen().isResultCorrect("4"));
- }
- }
將計算器的例子放在一邊,並跳入一個現實世界的例子:
- public class ChatTest extends AbstractTest {
- @Test
- public void sendMessageAndCheckHistoryTest() {
- login(Credentials.VALID_USER_CREDENTIALS);
- app.mainScreen().startChatWithUser(TEST_USERNAME);
- app.chatScreen().sendChatMessage(TEST_MESSAGE);
- app.chatScreen().navigateToHistoryScreen();
- assertTrue(app.historyScreen().containsMessage(TEST_MESSAGE));
- }
- @Test
- public void sendAndDeleteMessageThenCheckHistoryTest() {
- ...
- }
- }
正如你所看到的,當涉及多個屏幕時,這種模式的目的變得清晰,方便起見。我們現在正在瀏覽我們從未見過的一系列屏幕,但是我們已經可以得到我們測試中發生了什么的一般概念。如果我們看看我們調用的屏幕方法的實現,我們將會更准確地了解發生了什么。事實上,我們可以在沒有這樣做的情況下收集一些信息是使用PageObject編寫測試的好處之一。
如果UI發生小的變化,我們可能不需要改動我們的測試方法:改動將在我們的屏幕方法之一發生。在這種變化頻繁的敏捷環境中,除了測試腳本的強大性外,還特別受歡迎。
您可以選擇自己的屏幕方法的復雜程度。擁有更多,更簡單的屏幕方法將導致更長,更詳細的測試方法,暴露更多的交互的復雜性。按照這種方法,上述方法看起來更像這樣:
- @Test
- public void sendMessageAndCheckHistoryTest() {
- login(Credentials.VALID_USER_CREDENTIALS);
- app.mainScreen().navigateToUserSelection();
- app.userSelectionScreen().selectUser(TEST_USERNAME);
- app.userProfileScreen().startChat();
- app.chatScreen().sendChatMessage(TEST_MESSAGE);
- app.chatScreen().navigateToMainScreen();
- app.mainScreen().navigateToHistoryScreen();
- assertTrue(app.historyScreen().containsMessage(TEST_MESSAGE));
- }
雖然這種方法顯示了屏幕之間的每個過渡,但是它可能很容易成為壓倒性的,如在這個例子中:
- public class CreateDocumentationWithSuggestionTest extends AbstractTest {
- @Test
- public void buildNewDocumentationWithSuggestions() {
- app.documentationScreen().navigateToSettings();
- app.settingsScreen().navigateToSuggestions();
- app.settingsScreen().activateSuggestions(SUGGESTIONS));
- app.settingsScreen().navigateToDocumentation();
- app.documentationScreen().createDocumentation();
- app.documentationCreationScreen().selectCultivation();
- app.documentationDetailsScreen().selectFields(TEST_CULTIVATION_1.getFields());
- app.documentationDetailsScreen().selectConsumables(TEST_CULTIVATION_1.getConsumables());
- app.documentationDetailsScreen().selectWorkers(TEST_CULTIVATION_1.getWorkers());
- app.documentationDetailsScreen().sendActivity();
- app.documentationScreen().createDocumentation();
- app.documentationCreationScreen().selectCultivation();
- Assert.assertTrue(app.documentationDetailsScreen().areSuggestedFieldsFilledOut(TEST_CULTIVATION_1));
- }
- ...
- }
您應該保持測試方法足夠短,以便您能夠一目了然地告訴他們做什么,而不用將所有內容都包裝到單一屏幕方法中。尋找平衡是編寫一個好的,可維護的測試套件的關鍵。
總結:
PageObject前期可能工作量有點多,但是后面都是照葫蘆畫瓢非常容易維護,所以使用起來性價比還是挺高的,相比直來直去的測試腳本也減少了大量的重復代碼