Appium自动化测试框架示例

Appium自动化测试框架

本文依赖前面那篇Appium的配置环境,讲述一个比较通用的基于Appium的自动化测试项目框架,本人Android开发,本文视角会偏向于Android平台,由于Appium是跨平台的自动化测试工具,本文讲述的项目框架依然适用于iOS平台的自动化测试方案,iOS开发可以参考,再次感谢本文参考文章的作者,谢谢你们的辛勤付出,以下是参考文章的链接,小伙伴们也可以参考:


正文开始

一,项目的目录结构

我们首先看一下这个测试项目的整个结构,每个目录的用途我会简要标明,然后一个一个文件讲述:
这里写图片描述

一,项目的主要代码文件

首先,我们将项目中需要用到的jar配置到pom.xml,使用maven去下载管理,以下是pom.xml的内容

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>**your groupid**</groupId>
    <artifactId>**your artifactid**</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>aldb</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.10</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.appium</groupId>
            <artifactId>java-client</artifactId>
            <version>4.1.2</version>
            <exclusions>
                <exclusion>
                    <groupId>org.seleniumhq.selenium</groupId>
                    <artifactId>selenium-java</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-configuration/commons-configuration -->
        <dependency>
            <groupId>commons-configuration</groupId>
            <artifactId>commons-configuration</artifactId>
            <version>1.10</version>
        </dependency>

        <dependency>
            <groupId>net.sourceforge.jexcelapi</groupId>
            <artifactId>jxl</artifactId>
            <version>2.6.12</version>
            <scope>provided</scope>
        </dependency>

        <!-- Includes the Sauce JUnit helper libraries -->
        <dependency>
            <groupId>com.saucelabs</groupId>
            <artifactId>sauce_junit</artifactId>
            <version>LATEST</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.thoughtworks.qdox/qdox -->
        <dependency>
            <groupId>com.thoughtworks.qdox</groupId>
            <artifactId>qdox</artifactId>
            <version>1.12.1</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.10-FINAL</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.5</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>2.53.0</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-remote-driver</artifactId>
            <version>2.53.0</version>
        </dependency>
        <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jetty</artifactId>
            <version>7.0.0.pre5</version>
        </dependency>

    </dependencies>

    <repositories>
        <repository>
            <id>saucelabs-repository</id>
            <url>https://repository-saucelabs.forge.cloudbees.com/release</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
     <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.0.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>test-jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <!-- 经过测试 maven-compiler-plugin 插件版本请使用3.3,否则在jenkins上无法执行测试 -->
                <version>3.3</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19.1</version>
                <configuration>
                    <argLine>-Dfile.encoding=UTF-8</argLine>
                    <argLine>-Xms1024m -Xmx1024m -XX:PermSize=128m -XX:MaxPermSize=128m</argLine>
                    <forkMode>never</forkMode>
                    <suiteXmlFiles>
                        <suiteXmlFile>testng.xml</suiteXmlFile>
                    </suiteXmlFiles>
                    <reportsDirectory>./result/test-report</reportsDirectory>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                    implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.android.aldb.mySql</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins> 
    </build>
</project>

配置后可能需要一定时间下载,耐心等待

因为是初次接触这个框架,我们还是用上篇的通讯录demo作为我们这个框架的学习素材,将apk包放入包目录:
这里写图片描述

准备各目录里的文件

一,base目录

这里写图片描述

下面是BasePrepare的代码:

public class BasePrepare {
    protected  AppiumDriver<WebElement> driver; 
    protected AppiumUtil appiumUtil;
    public static Logger logger = Logger.getLogger(BasePrepare.class);
    protected String platformName;
    protected String appFilePath;
    protected String appPackage;
    protected int elementTimeOut;
  @BeforeClass
  public void initTest(ITestContext context) throws MalformedURLException{
      //使log4j的配置生效,以便输出日志
      LogConfiguration.initLog(this.getClass().getSimpleName());
      //获取platform、appFilePath、appPackage的值,这个值是从testng的配置文件获取的
      if(null == context){
          logger.info("null == context");
      }else if(null == context.getCurrentXmlTest()){
          logger.info("null == context.getCurrentXmlTest()");
      }
      platformName = context.getCurrentXmlTest().getParameter("platformName");
      appFilePath = context.getCurrentXmlTest().getParameter("appFilePath");
      appPackage = context.getCurrentXmlTest().getParameter("appPackage");
      elementTimeOut = Integer.valueOf(context.getCurrentXmlTest().getParameter("elementTimeOut"));
      appiumUtil = new AppiumUtil();
      //调用SelectDriver类的selectDriver方法,生成driver对象
      driver = new SelectDriver().selectDriver(context,appiumUtil);


  }

  @AfterClass
  public void clenTest(){
      if(driver!=null){

          appiumUtil.closeApp(PropertiesDataProvider.getTestData(appFilePath, appPackage));//appium模式
         logger.info("请等待60秒,待下一个用例执行");

         try {
             Thread.sleep(60000);
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }

         // driver.quit(); //selendroid 模式
      }else{
          Assert.fail("driver没有获得对象,退出操作失败");


      }
  }

    /**
     * 测试数据提供者 - 方法
     * */
    @DataProvider(name = "testData")
    public Iterator<Object[]> dataFortestMethod() throws IOException {
        String moduleName = null; // 模块的名字
        String caseNum = null; // 用例编号
        String className = this.getClass().getName();
        int dotIndexNum = className.indexOf("."); // 取得第一个.的index
        int underlineIndexNum = className.indexOf("_"); // 取得第一个_的index

        if (dotIndexNum > 0) {
            /**这里的calssName原始值大概是这样的:
             * com.android.aldb.testcase.home.HomePage_002_UiCheck_Test
             * 那么下面这段代码className.substring(33, className.lastIndexOf("."))是什么意思?substring方法参数有两个
             * 一个开始位置,一个结束位置,33表示这个字符串的第33个位置,这个位置当前字符是l,className.lastIndexOf(".")表示返回这字符串最后一个.所在
             * 的位置,它是38,那么className.substring(33, className.lastIndexOf("."))可以转换成:className.substring(33, 38),最终取得的值是login,
             * 也就是moduleName的值
             * 
             * 
             * */
            moduleName = className.substring(26, className.lastIndexOf(".")); // 取到模块的名称
        }

        if (underlineIndexNum > 0) {
            //这个分析方法和moduleName的分析方法一样
            caseNum = className.substring(underlineIndexNum + 1, underlineIndexNum + 4); // 取到用例编号
        }
        //将模块名称和用例的编号传给 ExcelDataProvider ,然后进行读取excel数据
        return new ExcelDataProvider(moduleName, caseNum);
    }

}

这里主要关注两个方法,第一个是initTest方法,里面主要的一段是

appiumUtil = new AppiumUtil();
      //调用SelectDriver类的selectDriver方法,生成driver对象
      driver = new SelectDriver().selectDriver(context,appiumUtil);

这个构建driver的方法主要是用配置文件里的参数来给driver做一个初始化的配置,在整个测试案例的生命周期里便可由driver来控制。第二个方法即调用driver.clossApp方法来结束整个流程,第三个方法对于我们这些初学者暂时可以不了解,其实看代码也很简单就是从Excel里读一些数据作为测试流程的数据源,所以测试案例的命名也是有一定规则的,具体可结合代码与testcase包里的测试文件名研究

二,pagehelp目录

这里写图片描述

下面是ContactsHelper的代码

public class ContactsHelper {

    public static Logger logger=Logger.getLogger(ContactsHelper.class);
    /**
     * @author tangjun
     * @param appiumUtil Appium封装对象引用
     * @param byElement 要点击的元素By对象
     * @description 在首页上进行点击操作
     * */
    public static  void clickOnPage(AppiumUtil appiumUtil,By byElement){
        appiumUtil.click(byElement);


    }

    /**
     * 输入内容,一般用在edittext控件
     * @param appiumUtil
     * @param byElement
     * @param str
     */
    public static void typeInfo(AppiumUtil appiumUtil,By byElement,String str){
        appiumUtil.typeContent(byElement, str);


    }

}

代码很简单,就是一个点击,一个输入文本,两个方法都接受By 类型作为参数,那这个自然就指向pages目录里的文件了,看下一个目录

三,pages目录

这里写图片描述

这是Contacts的代码

public class Contacts {

    public static final By ADD_CONTACTS = By.id("com.example.android.contactmanager:id/addContactButton");//第一页添加联系人按钮
    public static final By CONTACT_NAME = By.id("com.example.android.contactmanager:id/contactNameEditText");//第二页contact name输入框
    public static final By CONTACT_PHONE = By.id("com.example.android.contactmanager:id/contactPhoneEditText");//第二页contact phone输入框
    public static final By CONTACT_EMAIL = By.id("com.example.android.contactmanager:id/contactEmailEditText");//第二页contact email输入框
    public static final By CONTACT_PHONE_SPINNER = By.id("com.example.android.contactmanager:id/contactPhoneTypeSpinner");//第二页住宅 家,选择器
    public static final By CONTACT_SPINNER_PHONE = By.xpath("//android.widget.FrameLayout[1]/android.widget.FrameLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.LinearLayout[2]/android.widget.ListView[1]/android.widget.CheckedTextView[3]");//第二页号码类型是手机号


    public static final By SAVE = By.id("com.example.android.contactmanager:id/contactSaveButton");//save按钮

}

不多解释,很简单的从页面抓取元素的方法,这些ID或XPath的值在上一篇中有提及,使用uiautomatorViewer可快速获取到

四,testcase目录

这里写图片描述

Contacts_001_addcontact_Test代码:

public class Contacts_001_addcontact_Test extends BasePrepare {
    @Test
    public void login() {

        appiumUtil.time(3000);
        ContactsHelper.clickOnPage(appiumUtil, Contacts.ADD_CONTACTS);
        appiumUtil.time(3000);
        ContactsHelper.typeInfo(appiumUtil, Contacts.CONTACT_NAME,"楚乔");
        appiumUtil.time(2000);
        ContactsHelper.typeInfo(appiumUtil, Contacts.CONTACT_PHONE,"18900000009");
        appiumUtil.time(2000);
        ContactsHelper.clickOnPage(appiumUtil, Contacts.CONTACT_PHONE_SPINNER);
        appiumUtil.time(2000);
        ContactsHelper.clickOnPage(appiumUtil, Contacts.CONTACT_SPINNER_PHONE);
        appiumUtil.time(2000);
        ContactsHelper.typeInfo(appiumUtil, Contacts.CONTACT_EMAIL,"xiner@chuqiaozhuan.com");
        appiumUtil.time(3000);
        ContactsHelper.clickOnPage(appiumUtil, Contacts.SAVE);


    }
}

这就是一个完整的测试流程代码,这也是这个目录为什么叫testcase的原因,代码中有很多段这样的代码appiumUtil.time(2000),这个方法就是让线程睡眠一定时间让UI操作有一定的时间间隔,而不至于因为线程执行太快导致页面操作流程无法这么迅速地完成,上面代码很多次提及appiumUtil类,而到现在我们还没有看到这个类的代码,我们把这个类放在最后一个目录中,见下一个目录。

五,utils目录

这里写图片描述

AppiumUtil文件代码:

public class AppiumUtil {

    public static AppiumDriver<WebElement> driver;
    public ITestResult it;
    /** 定义日志输出对象 */
    public static Logger logger = Logger.getLogger(AppiumUtil.class);

    /**
     * 获取driver
     * 
     * @throws MalformedURLException
     * @throws
     */
    public AppiumDriver<WebElement> getDriver(String url,
            DesiredCapabilities cap) throws MalformedURLException {
        driver = new AndroidDriver<WebElement>(new URL(url), cap);

        return driver;

    }

    public AppiumDriver<WebElement> selectDriver(AppiumUtil appiumUtil,
            String url, DesiredCapabilities cap) throws MalformedURLException {
        driver = appiumUtil.getDriver(url, cap);
        return driver;

    }

    /** 退出app */
    public void closeApp(String appName) {
        driver.closeApp();
        logger.info(appName + "已经关闭");
    }

    /** 退出移动浏览器 */
    public void quit() {
        driver.quit();
        logger.info("driver已被清理");
    }

    /** 通过By对象 去查找某个元素 */
    public WebElement findElement(By by) {
        return driver.findElement(by);
    }

    /**
     * 通过By对象 去查找一组元素
     * */
    public List<WebElement> findElements(By by) {
        return driver.findElements(by);
    }

    /** 清空元素内容 */
    public void clear(By byElement) {
        WebElement element = findElement(byElement);
        element.clear();
        logger.info("清空元素:" + getLocatorByElement(element, ">") + "上的内容");
    }

    /** 输入内容 */
    public void typeContent(By byElement, String str) {
        WebElement element = findElement(byElement);
        element.sendKeys(str);
        logger.info("在元素:" + getLocatorByElement(element, ">") + "输入内容:" + str);
    }

    /** 点击 */
    public void click(By byElement) {
        WebElement element = findElement(byElement);
        try {
            element.click();
            logger.info("点击元素:" + getLocatorByElement(element, ">"));
        } catch (Exception e) {
            logger.error("点击元素:" + getLocatorByElement(element, ">") + "失败", e);
            Assert.fail("点击元素:" + getLocatorByElement(element, ">") + "失败", e);
        }

    }

    /** 查找一个元素 - appium新增的查找元素方法 */
    public WebElement byFindElement(String locateWay, String locateValue) {
        WebElement element = null;
        switch (locateWay) {

        case "AccessibilityId":
            element = driver.findElementByAccessibilityId(locateValue);
            break;
        // case "AndroidUIAutomator":
        // element = driver.findElementByAndroidUIAutomator(locateValue);
        // break;
        case "ClassName":
            element = driver.findElementByClassName(locateValue);
            break;
        case "CSS":
            element = driver.findElementByCssSelector(locateValue);
            break;
        case "ID":
            element = driver.findElementById("com.yd.android.ydz:id/"
                    + locateValue);
            break;
        case "LinkText":
            element = driver.findElementByLinkText(locateValue);
            break;
        case "Name":
            element = driver.findElementByName(locateValue);
            break;
        case "PartialLinkText":
            element = driver.findElementByPartialLinkText(locateValue);
            break;
        case "TagName":
            element = driver.findElementByTagName(locateValue);
            break;
        case "Xpath":
            element = driver.findElementByXPath(locateValue);
            break;
        default:
            logger.error("定位方式:" + locateWay + "不被支持");
            Assert.fail("定位方式:" + locateWay + "不被支持");

        }
        return element;

    }

    /** 查找一组元素 - appium新增的查找元素方法 */
    public List<?> findElements(String locateWay, String locateValue) {
        List<?> element = null;
        switch (locateWay) {

        case "AccessibilityId":
            element = driver.findElementsByAccessibilityId(locateValue);
            break;
        // case "AndroidUIAutomator":
        // element = driver.findElementsByAndroidUIAutomator(locateValue);
        // break;
        case "ClassName":
            element = driver.findElementsByClassName(locateValue);
            break;
        case "CSS":
            element = driver.findElementsByCssSelector(locateValue);
            break;
        case "ID":
            element = driver.findElementsById(locateValue);
            break;
        case "LinkText":
            element = driver.findElementsByLinkText(locateValue);
            break;
        case "Name":
            element = driver.findElementsByName(locateValue);
            break;
        case "PartialLinkText":
            element = driver.findElementsByPartialLinkText(locateValue);
            break;
        case "TagName":
            element = driver.findElementsByTagName(locateValue);
            break;
        case "Xpath":
            element = driver.findElementsByXPath(locateValue);
            break;
        default:
            logger.error("定位方式:" + locateWay + "不被支持");
            Assert.fail("定位方式:" + locateWay + "不被支持");

        }
        return element;

    }

    /** 获取文本1 */
    public String getText(By by) {
        return findElement(by).getText().trim();
    }

    /** 获取文本2 */
    public String getText(String locateWay, String locateValue) {
        String str = "";
        switch (locateWay) {

        case "AccessibilityId":
            str = driver.findElementByAccessibilityId(locateValue).getText()
                    .trim();
            break;
        // case "AndroidUIAutomator":
        // str =
        // driver.findElementByAndroidUIAutomator(locateValue).getText().trim();
        // break;
        case "ClassName":
            str = driver.findElementByClassName(locateValue).getText().trim();
            break;
        case "CSS":
            str = driver.findElementByCssSelector(locateValue).getText().trim();
            break;
        case "ID":
            str = driver.findElementById(locateValue).getText().trim();
            break;
        case "LinkText":
            str = driver.findElementByLinkText(locateValue).getText().trim();
            break;
        case "Name":
            str = driver.findElementByName(locateValue).getText().trim();
            break;
        case "PartialLinkText":
            str = driver.findElementByPartialLinkText(locateValue).getText()
                    .trim();
            break;
        case "TagName":
            str = driver.findElementByTagName(locateValue).getText().trim();
            break;
        case "Xpath":
            str = driver.findElementByXPath(locateValue).getText().trim();
            break;
        default:
            logger.error("定位方式:" + locateWay + "不被支持");
            Assert.fail("定位方式:" + locateWay + "不被支持");

        }
        return str;

    }

    /** 提交 */
    public void submit(By by) {
        WebElement element = findElement(by);
        try {
            element.submit();
        } catch (Exception e) {
            logger.error("在元素:" + getLocatorByElement(element, ">")
                    + "做的提交操作失败", e);
            Assert.fail(
                    "在元素:" + getLocatorByElement(element, ">") + "做的提交操作失败", e);
        }
        logger.info("在元素:" + getLocatorByElement(element, ">") + "做了提交操作");
    }

    /**
     * 获得webview页面的标题
     * */
    public String getTitle() {
        return driver.getTitle();
    }

    /**
     * 获得元素 属性的文本
     * */
    public String getAttributeText(By elementLocator, String attribute) {
        return findElement(elementLocator).getAttribute(attribute).trim();
    }

    /**
     * 在给定的时间内去查找元素,如果没找到则超时,抛出异常
     * */
    public void waitForElementToLoad(int elementTimeOut, final By By) {
        logger.info("开始查找元素[" + By + "]");
        try {
            (new WebDriverWait(driver, elementTimeOut))
                    .until(new ExpectedCondition<Boolean>() {

                        public Boolean apply(WebDriver driver) {
                            WebElement element = driver.findElement(By);
                            return element.isDisplayed();
                        }
                    });
        } catch (TimeoutException e) {
            logger.error("超时!! " + elementTimeOut + " 秒之后还没找到元素 [" + By + "]");
            Assert.fail("超时!! " + elementTimeOut + " 秒之后还没找到元素 [" + By + "]");

        }
        logger.info("找到了元素 [" + By + "]");
    }

    /**
     * 判断文本是不是和需求要求的文本一致
     * **/
    public void isTextCorrect(String actual, String expected) {
        try {
            Assert.assertEquals(actual, expected);
        } catch (AssertionError e) {
            logger.error("期望的结果是 [" + expected + "] 但是找到了 [" + actual + "]");
            Assert.fail("期望的结果是 [" + expected + "] 但是找到了 [" + actual + "]");

        }
        logger.info("找到了期望的结果: [" + expected + "]");

    }

    // 时间
    public static void time(int t) {
        try {
            Thread.sleep(t);

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }

    }

    /* 向上滑动10次 */
    public static void scorllUp() {
        int x = driver.manage().window().getSize().width;
        int y = driver.manage().window().getSize().height;
        int during;
        try {
            for (int i = 1; i < 10; i++) {
                Thread.sleep(3000);
                driver.swipe(x / 2, y * 9 / 10, x / 2, y / 10, 500);
                Thread.sleep(3000);
            }

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }

    }

    // 只滑动一次
    public void scorllUp1() {
        int x = driver.manage().window().getSize().width;
        int y = driver.manage().window().getSize().height;
        int during;
        try {

            Thread.sleep(1000);
            driver.swipe(x / 2, y * 9 / 10, x / 2, y / 10, 1000);
            Thread.sleep(1000);

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }

    }

    // 向上滑动一次
    public void scorllUp4() {
        int x = driver.manage().window().getSize().width;
        int y = driver.manage().window().getSize().height;
        int during;
        try {

            Thread.sleep(1000);
            driver.swipe(x / 2, y / 10, x / 2, y * 9 / 10, 1000);
            Thread.sleep(1000);

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }

    }

    public void scorllUp2() {
        int x = driver.manage().window().getSize().width;
        int y = driver.manage().window().getSize().height;
        int during;
        try {

            Thread.sleep(3000);
            driver.swipe(x / 2, y / 10, x / 2, y * 9 / 10, 500);
            Thread.sleep(3000);

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }

    }

    /**
     * 向左滑动
     */
    public void scorllUp3() {
        int x = driver.manage().window().getSize().width;
        int y = driver.manage().window().getSize().height;
        int during;
        try {

            driver.swipe(x * 9 / 10, y / 2, x / 10, y / 2, 2000);

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }

    }

    // 判断关键字是否存在
    public static boolean getPageSouce(String aaa) {
        return driver.getPageSource().contains(aaa);
    }

    // 关键字封装
    public static WebElement byId(AppiumDriver driver, String aaa) {
        return byId(driver, aaa, "", "");
    }

    /**
     * 
     * @param driver
     * @param aaa
     *            ("com.yd.android.ydz:id/" + )
     * @param bbb
     *            ("com.yd.android.camera:id/" + )
     * @return
     */
    public static WebElement byId(AppiumDriver driver, String aaa, String bbb,
            String ccc) {
        By by = null;
        if (StringUtils.isNotEmpty(aaa)) {
            by = By.id("com.yd.android.ydz:id/" + aaa);
        } else if (StringUtils.isNotEmpty(bbb)) {
            // 小米
            by = By.id("com.android.camera:id/" + bbb);
        } else if (StringUtils.isNotEmpty(ccc)) {
            // 魅族手机
            by = by.id("com.meizu.media.camera:id/" + ccc);
        } else {
            logger.info("参数错误无法初始化。。。");
        }
        return driver.findElement(by);
    }

    /**
     * 暂停当前用例的执行,暂停的时间为:sleepTime
     * */
    public void pause(int sleepTime) {
        if (sleepTime <= 0) {
            return;
        }
        try {
            TimeUnit.SECONDS.sleep(sleepTime);
            logger.info("暂停:" + sleepTime + "秒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    /** 根据元素来获取此元素的定位值 */
    public String getLocatorByElement(WebElement element, String expectText) {
        String text = element.toString();
        String expect = null;
        try {
            expect = text.substring(text.indexOf(expectText) + 1,
                    text.length() - 1);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("failed to find the string [" + expectText + "]");

        }

        return expect;

    }

    /**
     * 判断实际文本时候包含期望文本
     * 
     * @param actual
     *            实际文本
     * @param expect
     *            期望文本
     */
    public void isContains(String actual, String expect) {
        try {
            Assert.assertTrue(actual.contains(expect));
        } catch (AssertionError e) {
            logger.error("The [" + actual + "] is not contains [" + expect
                    + "]");
            Assert.fail("The [" + actual + "] is not contains [" + expect + "]");
        }
        logger.info("The [" + actual + "] is contains [" + expect + "]");
    }

    /** 跳转到webview页面 */
    public void switchWebview(int index) {
        Set<String> contexts = driver.getContextHandles();
        for (String context : contexts) {
            System.out.println(context);
            // 打印出来看看有哪些context
        }
        driver.context((String) contexts.toArray()[index]);

    }

    /** 跳转到webview页面 */
    public void switchWebview(String contextName) {
        try {
            Set<String> contexts = driver.getContextHandles();
            for (String context : contexts) {
                System.out.println(context);
                // 打印出来看看有哪些context
            }
            driver.context(contextName);
        } catch (NoSuchContextException nce) {
            logger.error("没有这个context:" + contextName, nce);
            Assert.fail("没有这个context:" + contextName, nce);
        }

    }

    /**
     * 执行JavaScript 方法
     * */
    public void executeJS(String js) {
        ((JavascriptExecutor) driver).executeScript(js);
        logger.info("执行JavaScript语句:[" + js + "]");
    }

    /**
     * 执行JavaScript 方法和对象 用法:seleniumUtil.executeJS("arguments[0].click();",
     * seleniumUtil.findElementBy(MyOrdersPage.MOP_TAB_ORDERCLOSE));
     * */
    public void executeJS(String js, Object... args) {
        ((JavascriptExecutor) driver).executeScript(js, args);
        logger.info("执行JavaScript语句:[" + js + "]");
    }

    /** 检查元素是不是存在 */
    public boolean doesElementsExist(By byElement) {
        try {
            findElement(byElement);
            return true;
        } catch (NoSuchElementException nee) {

            return false;
        }

    }

    /** 长按操作 */
    public void longPress(By by) {
        TouchAction tAction = new TouchAction(driver);
        tAction.longPress(findElement(by)).perform();
    }

    /** 滑动 */
    public void swipe(int beginX, int beginY, int endX, int endY) {
        TouchAction tAction = new TouchAction(driver);
        try {
            tAction.press(beginX, beginY).moveTo(endX, endY).release()
                    .perform();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /** 拖拽操作 */
    public void DragAndDrop(By dragElement, By dropElement) {
        TouchAction act = new TouchAction(driver);
        act.press(findElement(dragElement)).perform();
        act.moveTo(findElement(dropElement)).release().perform();
    }

    /** 放大和缩小 */
    public void zoomAndPinch(int beginX, int beginY, int endX, int endY) {
        int scrHeight = driver.manage().window().getSize().getHeight();
        int scrWidth = driver.manage().window().getSize().getWidth();
        MultiTouchAction multiTouch = new MultiTouchAction(driver);
        TouchAction tAction0 = new TouchAction(driver);
        TouchAction tAction1 = new TouchAction(driver);
        tAction0.press(scrWidth / 2, scrHeight / 2).waitAction(1000)
                .moveTo(beginX, beginY).release();
        tAction1.press(scrWidth / 2, scrHeight / 2 + 40).waitAction(1000)
                .moveTo(endX, endY).release();
        multiTouch.add(tAction0).add(tAction1);
        multiTouch.perform();

    }

    /** app置于后台运行 */
    public void runBackgound(int runTimes) {
        driver.runAppInBackground(runTimes);

    }

    /** 收起键盘 */
    public void hideKeyboard() {
        driver.hideKeyboard();
        logger.info("虚拟键盘已经收起");

    }

    /** 安装app */
    public void instalApp(String appPath) {
        try {
            driver.installApp(appPath);
        } catch (Exception e) {
            logger.error("app安装失败", e);
            Assert.fail("app安装失败", e);
        }
    }

    /** app是否安装 */
    public boolean isAppInstalled(String appPackage) {

        if (driver.isAppInstalled(appPackage)) {
            logger.info(appPackage + ":已经安装");
            return true;
        } else {
            logger.info(appPackage + ":未安装");
            return false;
        }
    }

    /** 页面过长时候滑动页面 window.scrollTo(左边距,上边距); */
    public void scrollPage(int x, int y) {
        String js = "window.scrollTo(" + x + "," + y + ");";
        ((JavascriptExecutor) driver).executeScript(js);
    }

    public static void coorDinate(AppiumDriver driver, int x, int y,
            int duration) {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        HashMap<String, Integer> tapObject = new HashMap<String, Integer>();
        tapObject.put("x", x);
        tapObject.put("y", y);
        tapObject.put("duration", duration);
        js.executeScript("mobile: tap", tapObject);
    }

    /**
     * 获取随机数
     */
    public static int getNum(int start, int end) {
        return (int) (Math.random() * end + start);
    }

    /**
     * 获取随机英文字母
     */
    private static char testZimu() {
        String chars = "abcdefghijklmnopqrstuvwxyz";
        return chars.charAt((int) (Math.random() * 26));
    }

    /**
     * seekbar拖动
     * 
     * @param appiumUtil
     */
    public static void seekBar(AppiumUtil appiumUtil) {
        WebElement Slider = driver.findElement(By
                .id("com.aldb.android:id/loan_total_seekbar"));
        int start = Slider.getLocation().getX();
        int end = start + Slider.getSize().getWidth();
        int y = Slider.getLocation().getY();

        TouchAction act = new TouchAction(driver);
        act.press(start, y).waitAction(800).moveTo(end - 1, y).release()
                .perform();

    }

    /**
     * 切换到webview界面
     */
    public static void handle() {
        Set<String> contexts = driver.getContextHandles();

        for (String context : contexts) {
            logger.info(context);

        }

        driver.context((String) contexts.toArray()[1]);

    }

    /**
     * 切换到app端
     */
    public static void handles() {
        Set<String> contexts = driver.getContextHandles();
        for (String context : contexts) {
            logger.info(context);

        }
        driver.context((String) contexts.toArray()[0]);
    }

    public static void wait1() {
        try {
            final WebDriver wait = (WebDriver) new WebDriverWait(driver, 10);
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }

    }

}

代码注释的很清楚,可以大略了解一下,封装了一下大部分页面操作会用到的方法,主要的几个就是点击,输入,滑动等等几个操作,然后我们先看SelectDriver的代码

public class SelectDriver {
        //声明driver
        public  AppiumDriver<WebElement> driver; 
        //声明DesiredCapabilities

        //声明ITestContext,用于获取testng配置文件内容
        public ITestContext testContext;
        //appium server地址
        public String serverURL;
        //测试引擎名字
        public String automationName;
        //测试平台名字
        public String platformName;
        //测试平台版本号
        public String platformVersion;
        //设备名字
        public String deviceName;

        //android app路径
        public String androidAppPath;
        //android app的 package
        public String appPackage;
        //android app的activity
        public String appActivity;
        //安卓独有 - 是否使用unicode键盘,使用此键盘可以输入中文字符
        public boolean unicodeKeyboard;
        //android独有 - 是否重置键盘,如果设置了unicodeKeyboard键盘,可以将此参数设置为true,然后键盘会重置为系统默认的
        public boolean resetKeyboard;
        //是否覆盖已有的seesssion,这个用于多用例执行,如果不设置的话,会提示前一个session还没有结束,用例就不能继续执行了
        public boolean sessionOverride;
        //暂停的等待时间
        public int sleepTime;
        //元素等待超时时间
        public int elementTimeOut;
        //app文件路径,主要存储的是app的名字
        public String appFilePath;
        //webview的名字或者叫标识符,一般以WEBVIEW开头,例如WEBVIEW_com.microsoft.bing
        public final static String WEBVIEW_NAME = null;
        //原生app的名字或者标识符,一般是NATIVE_APP
        public final static String NATIVEAPP_NAME = null;

        public String udid;
        //实例化本类的日志输出对象
        public static Logger logger = Logger.getLogger(SelectDriver.class);

        public  AppiumDriver<WebElement> selectDriver(ITestContext context,AppiumUtil appiumUtil) throws MalformedURLException{
              //通过testng的xml文件获取serverURL参数值,并赋给  serverURL变量
              serverURL = context.getCurrentXmlTest().getParameter("serverURL");
              //通过testng的xml文件获取automationName参数值,并赋给  automationName变量
              automationName = context.getCurrentXmlTest().getParameter("automationName");
              //通过testng的xml文件获取platformName参数值,并赋给  platformName变量
              platformName = context.getCurrentXmlTest().getParameter("platformName");
              //通过testng的xml文件获取platformVersion参数值,并赋给  platformVersion变量
              platformVersion = context.getCurrentXmlTest().getParameter("platformVersion");
              //通过testng的xml文件获取deviceName参数值,并赋给  deviceName变量
              deviceName = context.getCurrentXmlTest().getParameter("deviceName");
              //通过testng的xml文件获取androidAppPath参数值,并赋给  androidAppPath变量
              androidAppPath = context.getCurrentXmlTest().getParameter("androidAppPath");


              //通过testng的xml文件获取appPackage参数值,并赋给  appPackage变量
              appPackage = context.getCurrentXmlTest().getParameter("appPackage");
              //通过testng的xml文件获取appActivity参数值,并赋给  appActivity变量
              appActivity = context.getCurrentXmlTest().getParameter("appActivity");
              //通过testng的xml文件获取unicodeKeyboard参数值,并赋给  unicodeKeyboard变量
              unicodeKeyboard = Boolean.parseBoolean(context.getCurrentXmlTest().getParameter("unicodeKeyboard"));
              //通过testng的xml文件获取resetKeyboard参数值,并赋给  resetKeyboard变量
              resetKeyboard = Boolean.parseBoolean(context.getCurrentXmlTest().getParameter("resetKeyboard"));

              //通过testng的xml文件获取sleepTime参数值,并赋给  sleepTime变量
              sleepTime = Integer.valueOf(context.getCurrentXmlTest().getParameter("sleepTime"));
              //通过testng的xml文件获取elementTimeOut参数值,并赋给  elementTimeOut变量
              elementTimeOut = Integer.valueOf(context.getCurrentXmlTest().getParameter("elementTimeOut"));
              //通过testng的xml文件获取appFilePath参数值,并赋给  appFilePath变量
              appFilePath = context.getCurrentXmlTest().getParameter("appFilePath");

              sessionOverride = Boolean.valueOf(context.getCurrentXmlTest().getParameter("sessionOverride"));
              udid=context.getCurrentXmlTest().getParameter("udid");

              this.testContext = context;
              DesiredCapabilities cap  = new DesiredCapabilities();
              //告诉测试程序,当前项目目录在哪里

              //设置capability,以便和appium创建session
              cap.setCapability("platformName",platformName);
              cap.setCapability("platformVersion",platformVersion);
              cap.setCapability("androidAppPath", androidAppPath);
              cap.setCapability("deviceName",deviceName);
              cap.setCapability("sessionOverride", sessionOverride);
              cap.setCapability("udid", udid);


                cap.setCapability("unicodeKeyboard", unicodeKeyboard);
                cap.setCapability("resetKeyboard", resetKeyboard);
                cap.setCapability("automationName",automationName);
                cap.setCapability("appPackage", appPackage);
                cap.setCapability("appActivity", appActivity);
                  driver = appiumUtil.getDriver(serverURL, cap);
                  testContext.setAttribute("APPIUM_DRIVER", driver);

                  logger.info(PropertiesDataProvider.getTestData(appFilePath, appPackage)+"已经启动");

                  driver.manage().timeouts().implicitlyWait(elementTimeOut, TimeUnit.SECONDS);
                  return driver;

        }



    }

从xml 的配置文件中取得所需配置参数,使用appiumUtil类构建出driver供BasePrepare调用,这个配置文件根据maven的运行方法,放在最外层的目录,命名为testng.xml代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="魔法现金" parallel="tests" thread-count="1">

    <!--server地址 -->
    <parameter name="serverURL" value="http://127.0.0.1:4723/wd/hub" />
    <!--automationName为selendroid or appium,如果sdk版本>=17请使用appium;如果sdk版本<=17请使用selendroid -->
    <parameter name="automationName" value="Appium" />
    <!-- 测试平台 iOS和Android -->
    <parameter name="platformName" value="Android" />
    <!-- 平台版本 -->
    <parameter name="platformVersion" value="4.4" />
    <!-- 设备名字,可随意起名字,但是要有意义 -->
    <parameter name="deviceName" value="xiaomi" />
    <!-- android app路径 -->
    <parameter name="androidAppPath" value="res/app/android/ContactManager.apk" />

    <!--app的包 -->
    <parameter name="appPackage" value="com.example.android.contactmanager" />
    <!--app的 activity -->
    <parameter name="appActivity" value=".ContactManager" />
    <!--是否支持unicode输入设置为true可以输入中文字符 -->
    <parameter name="unicodeKeyboard" value="true" />
    <!-- 重置键盘输入法 -->
    <parameter name="resetKeyboard" value="true" />
    <!--设备UDID iPhone真机使用或者android并行测试可以使用 -->
    <parameter name="udid" value="3DN6T16928001972" />
    <!-- 设置为true之后会覆盖当前session -->
    <parameter name="sessionOverride" value="true" />
    <!-- 进程等待1秒中的控制时间,单位是秒 -->
    <parameter name="sleepTime" value="1" />
    <!-- 页面元素15秒不出现超时时间 -->
    <parameter name="elementTimeOut" value="15" />
    <!-- app属性文件 -->
    <parameter name="appFilePath" value="res/properties/app.properties" />

       <test name="添加通讯录模块" preserve-order="true"> 
       <!--  <packages>
            <package name="包目录" />
            </packages>  -->
            <classes>
                <class name="Contacts_001_addcontact_Test所在包目录" />
                </classes> 
        </test>

</suite> <!-- Suite -->

这个便是程序的执行入口,遵循maven的项目管理方式,将utils包中的几个类补充一下:

ExcelDataProvider的代码:主要用途是从Excel表读取数据

/**
 * @author tangjun
 * @description: 读取Excel数据<br>
 *               说明:<br>
 *               Excel放在Data文件夹下<br>
 *               Excel命名方式:测试类名.xls<br>
 *               Excel的sheet命名方式:测试方法名<br>
 *               Excel第一行为Map键值<br>
 */
public class ExcelDataProvider implements Iterator<Object[]> {

    private Workbook book = null;
    private Sheet sheet = null;
    private int rowNum = 0;
    private int currentRowNo = 0;
    private int columnNum = 0;
    private String[] columnnName;
    private String path = null;
    private InputStream inputStream = null;
    public static Logger logger = Logger.getLogger(ExcelDataProvider.class.getName());

    /* 
     * @description 
     * 2个参数:<br>
     * moduleName - 模块的名称
     * caseNum - 测试用例编号
     **/
    public ExcelDataProvider(String moduleName, String caseNum) {

        try {
            //文件路径
            path = "data/"+moduleName+".xls";

             inputStream = new FileInputStream(path);

            book = Workbook.getWorkbook(inputStream);
            // sheet = book.getSheet(methodname);
            sheet = book.getSheet(caseNum); // 读取第一个sheet
            rowNum = sheet.getRows(); // 获得该sheet的 所有行
            Cell[] cell = sheet.getRow(0);// 获得第一行的所有单元格
            columnNum = cell.length; // 单元格的个数 值 赋给 列数
            columnnName = new String[cell.length];// 开辟 列名的大小

            for (int i = 0; i < cell.length; i++) {
                columnnName[i] = cell[i].getContents().toString(); // 第一行的值
                                                                    // 被赋予为列名
            }
            this.currentRowNo++;

        } catch (FileNotFoundException e) {
            logger.error("没有找到指定的文件:" + "[" + path + "]");
            Assert.fail("没有找到指定的文件:" + "[" + path + "]");
        } catch (Exception e) {
            logger.error("不能读取文件: [" + path + "]",e);
            Assert.fail("不能读取文件: [" + path + "]");
        }
    }
    /**是否还有下个内容*/

    public boolean hasNext() {

        if (this.rowNum == 0 || this.currentRowNo >= this.rowNum) {

            try {
                inputStream.close();
                book.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        } else {
            // sheet下一行内容为空判定结束
            if ((sheet.getRow(currentRowNo))[0].getContents().equals(""))
                return false;
            return true;
        }
    }
    /**返回内容*/
    public Object[] next() {

        Cell[] c = sheet.getRow(this.currentRowNo);

        Map<String, String> data = new HashMap<String, String>();

        for (int i = 0; i < this.columnNum; i++) {

            String temp = "";

            try {
                temp = c[i].getContents().toString();
            } catch (ArrayIndexOutOfBoundsException ex) {
                temp = "";
            }

            data.put(this.columnnName[i], temp);
        }
        Object object[] = new Object[1];
        object[0] = data;
        this.currentRowNo++;
        return object;
    }

    public void remove() {
        throw new UnsupportedOperationException("remove unsupported.");
    }


}

LogConfiguration的代码:主要用途是生成每条用例的日志

 /* @decription 动态生成各个模块中的每条用例的日志,运行完成用例之后请到result/log目录下查看
 * */
public class LogConfiguration {

        public static void initLog(String fileName){
            //获取到模块名字
            String founctionName = getFunctionName(fileName);
            //声明日志文件存储路径以及文件名、格式
            final String logFilePath  = "./result/log/"+founctionName+"/"+fileName+".log";  
            Properties prop = new Properties();
            //配置日志输出的格式
            prop.setProperty("log4j.rootLogger","info, toConsole, toFile");
            prop.setProperty("log4j.appender.file.encoding","UTF-8" );
            prop.setProperty("log4j.appender.toConsole","org.apache.log4j.ConsoleAppender");
            prop.setProperty("log4j.appender.toConsole.Target","System.out");
            prop.setProperty("log4j.appender.toConsole.layout","org.apache.log4j.PatternLayout ");
            prop.setProperty("log4j.appender.toConsole.layout.ConversionPattern","[%d{yyyy-MM-dd HH:mm:ss}] [%p] %m%n");        
            prop.setProperty("log4j.appender.toFile", "org.apache.log4j.DailyRollingFileAppender");
            prop.setProperty("log4j.appender.toFile.file", "./result/log/"+founctionName+"/"+fileName+".log");
            prop.setProperty("log4j.appender.toFile.append", "false");
            prop.setProperty("log4j.appender.toFile.Threshold", "info");
            prop.setProperty("log4j.appender.toFile.layout", "org.apache.log4j.PatternLayout");
            prop.setProperty("log4j.appender.toFile.layout.ConversionPattern", "[%d{yyyy-MM-dd HH:mm:ss}] [%p] %m%n");
            //使配置生效
            PropertyConfigurator.configure(prop);

        }


     //**取得模块名字*/
        public static String getFunctionName(String fileName){
            String functionName = null; 
            /*int firstUndelineIndex = fileName.indexOf("_"); */
            functionName = fileName.substring(0, fileName.indexOf("_"));
            return functionName;

    }


}

PropertiesDataProvider的代码,主要用来从.properties文件中读取相关测试数据

* @Desription 从.properties文件中读取相关测试数据<br>
 * 
 * */
public class PropertiesDataProvider {

    public static String getTestData(String configFilePath, String key) {
        Configuration config = null;
        try {
            config = new PropertiesConfiguration(configFilePath);
        } catch (ConfigurationException e) {
            e.printStackTrace();
        }
        return String.valueOf(config.getProperty(key));

    }
}

这么多文件看起来相当的混乱,所以我把这个demo上传到了百度云,地址是链接: https://pan.baidu.com/s/1jIqTP4a 密码: sm36,还有在这补充一点的是项目是用了Intelli IDEA工具,并不是上一篇中直接在Android项目中构造一个JavaLibrary,很多地方也只是贴了代码并未详细描述,建议下载demo运行测试学习,谢谢

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值