使用appium和testng实现Android自动截图
简单介绍
需求场景是:当测试安卓应用的脚本得到失败结果时,对当前手机屏幕截图,便于查找问题。
实现方式是: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); } }
简单总结
要想实现自动截图,需要做到三点:
- 继承带有截图方法的父类;
- 调用父类的init方法更新driver;
- 实现ITestListener接口或者重载TestListenerAdapter,并启用自定义的监听类;
留下的问题
测试Android应用和iOS应用需要使用不同的driver,进而需要定义两个测试类的父类。能不能只定义一个父类呢?