概述
原文地址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前期可能工作量有点多,但是后面都是照葫芦画瓢非常容易维护,所以使用起来性价比还是挺高的,相比直来直去的测试脚本也减少了大量的重复代码
转载于:https://www.cnblogs.com/111testing/p/8373335.html
最后
以上就是独特小蘑菇为你收集整理的Appium PageObject的全部内容,希望文章能够帮你解决Appium PageObject所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复