首页 > 技术文章 > 使用appium和testng实现Android自动截图

superbaby11 2017-02-21 20:23 原文

简单介绍

需求场景是:当测试安卓应用的脚本得到失败结果时,对当前手机屏幕截图,便于查找问题。

实现方式是:1)定义一个父类UITest,作为所有测试类的父类。在父类中UITest中定义一个截图的方法,所有的子类就都可以使用这个方法了。2)实现testng的ITestListener接口,参考这里,在这个接口的onTestFailure方法中调用测试类的截图方法。

定义测试脚本的父类

package main.java.com.dbyl.library.utils;

/**
 * Created by wwh on 17/2/16.
 */
/**
 *
 */


import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import io.appium.java_client.android.AndroidDriver;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;


/**
 * @author 
 *
 */
public class UITest {
    private AndroidDriver driver;
    Log log = new Log(this.getClass());

    public AndroidDriver getDriver() {
        return driver;
    }

    /**
     * init test case
     *
     * @param driver
     */
    public void setDriver(AndroidDriver driver) {
        this.driver = driver;//把子类的driver传进来,更新父类driver
    }

    public void init(AndroidDriver driver) {
        setDriver(driver);
    }
    

    /**
     *  take screenshot
     */
    public void takeScreenShot() {
        SimpleDateFormat sf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
        Calendar cal = Calendar.getInstance();
        Date date = cal.getTime();
        String dateStr = sf.format(date);
        String path = this.getClass().getSimpleName() + "_" + dateStr + ".png";//用类名和日期作为截图的文件名
        takeScreenShot((TakesScreenshot) this.getDriver(), path);
    }

    /**
     * take screenshot
     * @param drivername
     * @param path
     */
    public void takeScreenShot(TakesScreenshot drivername, String path) {
        // this method will take screen shot ,require two parameters ,one is
        // driver name, another is file name

        log.info("take screenshot");
        File scrFile = drivername.getScreenshotAs(OutputType.FILE);
        // Now you can do whatever you need to do with it, for example copy
        try {
            log.info("save snapshot path is:"  + path);
            FileUtils.copyFile(scrFile, new File( path));
        } catch (Exception e) {
            log.error("Can't save screenshot");
            e.printStackTrace();
        } finally {
            log.info("screen shot finished");
        }
    }

}

上面代码中,使用了AndroidDriver类型的driver,适用于安卓应用测试脚本的父类。如果测试iOS应用,则使用iOSDriver类型的driver。

继承父类的测试脚本

package main.java.com.dbyl.library.utils;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.remote.AndroidMobileCapabilityType;
import io.appium.java_client.remote.AutomationName;
import io.appium.java_client.remote.MobileCapabilityType;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.testng.Assert;
import org.testng.annotations.*;

import java.io.File;
import java.net.URL;


/**
 * Created by wwh on 17/2/20.
 */
@Listeners(main.java.com.dbyl.library.utils.CustomTestngListener.class)//通过注解调用我们自己实现的类
public class TestScreenshot extends UITest{//继承父类UITest
    private AndroidDriver<MobileElement> driver;

    @Test
    public void Demo() throws Exception {
// set up appium
        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.APPIUM);
//for native app set null, for web test please set chrome or firefox
        capabilities.setCapability(CapabilityType.BROWSER_NAME, "");
        capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
        capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
//simulator version 4.4
        capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "4.4");
// if no need install don't add this
        File classpathRoot = new File("/Users/wwh/IdeaProjects");
//        File classpathRoot = new File(System.getProperty("user.dir"));
        File appDir = new File(classpathRoot, "apps");
        File app = new File(appDir, "apppiumDemo.apk");
        capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());

//package name
        capabilities.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "cn.dbyl.appiumdemo");
// // support Chinese
        capabilities.setCapability("unicodeKeyboard", "True");
        capabilities.setCapability("resetKeyboard", "True");
// no need sign
        capabilities.setCapability("noSign", "True");
//launcher activity
        capabilities.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, ".MainActivity");
        String url = "http://localhost:4723/wd/hub";
        driver = new AndroidDriver<MobileElement>(new URL(url), capabilities);
        super.init(driver);//在driver赋值后,需要将driver传给父类。

        MobileElement text = driver.findElementById("cn.dbyl.appiumdemo:id/text11");//这里将ID值由text1改为text11,所以这一句会报错
        Assert.assertEquals(text.getText(), "appiumDemo");

        MobileElement button = driver.findElementByClassName("android.widget.Button");
        button.click();
        text = driver.findElementById("cn.dbyl.appiumdemo:id/text1");
        Assert.assertEquals(text.getText(), "You just click the button");

    }

    @AfterTest
    public void Teardown(){
        driver.quit();
    }

}

TestListenerAdapter类实现了ITestListener接口。下面是重载TestListenerAdapter类的代码

package main.java.com.dbyl.library.utils;

/**
 * Created by wwh on 17/2/17.
 */
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;

/**
 *
 * @author 
 *
 */
public class CustomTestngListener extends TestListenerAdapter {
    Log log = new Log(this.getClass());

    @Override
    public void onTestSuccess(ITestResult tr) {
        log.info("Test Success");
        super.onTestSuccess(tr);
    }

    @Override
    public void onTestFailure(ITestResult tr) {
        log.error("One Test Failure");
        super.onTestFailure(tr);
        takeScreenShot(tr);
    }

    private void takeScreenShot(ITestResult tr) {
        UITest b = (UITest) tr.getInstance();//ITestResult类型可以返回当前测试类的一个实例
        b.takeScreenShot();//借助这个实例,调用其截图方法

    }

    @Override
    public void onTestSkipped(ITestResult tr) {
        log.error("Test Skipped");
        super.onTestSkipped(tr);
    }

    @Override
    public void onTestStart(ITestResult result) {
        log.info("One Test Start");
        super.onTestStart(result);
    }

    @Override
    public void onStart(ITestContext testContext) {
        log.info("Before Any Test Start");
        super.onStart(testContext);
    }

    @Override
    public void onFinish(ITestContext testContext) {
        log.info("After All Tests Finish");
        super.onFinish(testContext);
    }

}

简单总结

要想实现自动截图,需要做到三点:

  1. 继承带有截图方法的父类;
  2. 调用父类的init方法更新driver;
  3. 实现ITestListener接口或者重载TestListenerAdapter,并启用自定义的监听类;

留下的问题

测试Android应用和iOS应用需要使用不同的driver,进而需要定义两个测试类的父类。能不能只定义一个父类呢?

推荐阅读