Appium自动化测试框架示例

来源:互联网 发布:超级优化主角几个老婆 编辑:程序博客网 时间:2024/05/16 13:06

Appium自动化测试框架

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

  • http://www.w2bc.com/article/182463
  • http://www.w2bc.com/article/182544

正文开始

一,项目的目录结构

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

一,项目的主要代码文件

首先,我们将项目中需要用到的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运行测试学习,谢谢

原创粉丝点击