[Android测试] AS+Appium+Java+Win 自动化测试之六 Appium的Java测试脚本封装
2016-09-21 19:42
483 查看
一、为什么需要封装?
封装的本意就是为了方便、简洁。二、Android的显式等待封装
1. AndroidDriverWait.java
package com.example.base; /** * Created by LITP on 2016/9/8. */ import org.openqa.selenium.NotFoundException; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.support.ui.Clock; import org.openqa.selenium.support.ui.FluentWait; import org.openqa.selenium.support.ui.Sleeper; import org.openqa.selenium.support.ui.SystemClock; import java.util.concurrent.TimeUnit; import io.appium.java_client.android.AndroidDriver; public class AndroidDriverWait extends FluentWait<AndroidDriver> { public final static long DEFAULT_SLEEP_TIMEOUT = 500; private final WebDriver driver; public AndroidDriverWait(AndroidDriver driver, long timeOutInSeconds) { this(driver, new SystemClock(), Sleeper.SYSTEM_SLEEPER, timeOutInSeconds, DEFAULT_SLEEP_TIMEOUT); } public AndroidDriverWait(AndroidDriver driver, long timeOutInSeconds, long sleepInMillis) { this(driver, new SystemClock(), Sleeper.SYSTEM_SLEEPER, timeOutInSeconds, sleepInMillis); } public AndroidDriverWait(AndroidDriver driver, Clock clock, Sleeper sleeper, long timeOutInSeconds, long sleepTimeOut) { super(driver, clock, sleeper); withTimeout(timeOutInSeconds, TimeUnit.SECONDS); pollingEvery(sleepTimeOut, TimeUnit.MILLISECONDS); ignoring(NotFoundException.class); this.driver = driver; } @Override protected RuntimeException timeoutException(String message, Throwable lastException) { TimeoutException ex = new TimeoutException(message, lastException); ex.addInfo(WebDriverException.DRIVER_INFO, driver.getClass().getName()); if (driver instanceof RemoteWebDriver) { RemoteWebDriver remote = (RemoteWebDriver) driver; if (remote.getSessionId() != null) { ex.addInfo(WebDriverException.SESSION_ID, remote.getSessionId().toString()); } if (remote.getCapabilities() != null) { ex.addInfo("Capabilities", remote.getCapabilities().toString()); } } throw ex; } }
2.ExpectedCondition.java
接口package com.example.base; import com.google.common.base.Function; import io.appium.java_client.android.AndroidDriver; /** * Created by LITP on 2016/9/8. */ public interface ExpectedCondition<T> extends Function<AndroidDriver, T> {}
3. 使用
/** * 显示等待,等待Id对应的控件出现time秒,一出现马上返回,time秒不出现也返回 */ public AndroidElement waitAuto(By by, int time) { try { return new AndroidDriverWait(driver, time) .until(new ExpectedCondition<AndroidElement>() { @Override public AndroidElement apply(AndroidDriver androidDriver) { return (AndroidElement) androidDriver.findElements(by); } }); } catch (TimeoutException e) { Assert.fail("查找元素超时!! " + time + " 秒之后还没找到元素 [" + by.toString() + "]", e); return null; } }
三、Assert断言的封装
封装了 出现错误输出了异常信息但不终止程序的运行,会继续往下执行。1.Assertion.java
package com.example.base; import org.testng.Assert; import java.util.ArrayList; import java.util.List; /** * Created by LITP on 2016/9/21. */ public class Assertion { public static boolean flag = true; //是否有错误 public static List<Error> errors = new ArrayList<>(); //错误集合 /** * 验证值是否相等 * @param actual 第一个值 * @param expected 要对比的值 */ public static void verifyEquals(Object actual, Object expected){ try{ Assert.assertEquals(actual, expected); }catch(Error e){ errors.add(e); flag = false; } } /** * 验证值是否相等 * @param actual 第一个值 * @param expected 要对比的值 * @param message 出错时候的提示消息 */ public static void verifyEquals(Object actual, Object expected, String message){ try{ Assert.assertEquals(actual, expected, message); }catch(Error e){ errors.add(e); flag = false; } } }
2.AssertionListener.java
package com.example.base; import org.testng.ITestResult; import org.testng.TestListenerAdapter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static javafx.scene.input.KeyCode.T; /** * Created by LITP on 2016/9/21. */ public class AssertionListener extends TestListenerAdapter { /** * 测试方法开始的时候回调 * @param result */ @Override public void onTestStart(ITestResult result) { Assertion.flag = true; Assertion.errors.clear(); } /** * 测试终止时候回调 * @param tr */ @Override public void onTestFailure(ITestResult tr) { this.handleAssertion(tr); } /** * Test跳过 的时候执行 * @param tr */ @Override public void onTestSkipped(ITestResult tr) { this.handleAssertion(tr); } /** * Test运行完毕时候执行 * @param tr */ @Override public void onTestSuccess(ITestResult tr) { this.handleAssertion(tr); } private int index = 0; //错误行号 /** * 处理断言,每个Test执行完毕回调 * @param tr 测试结果 */ private void handleAssertion(ITestResult tr){ if(!Assertion.flag){ //为假,就是断言出错了就执行下面的 //获取异常 Throwable throwable = tr.getThrowable(); if(throwable==null){ throwable = new Throwable(); } //获取异常堆栈信息 StackTraceElement[] traces = throwable.getStackTrace(); //创建要输出的所有堆栈信息 StackTraceElement[] alltrace = new StackTraceElement[0]; //循环获取断言的异常信息, for (Error e : Assertion.errors) { //获取错误的堆栈数组信息 StackTraceElement[] errorTraces = e.getStackTrace(); // StackTraceElement[] et = getKeyStackTrace(tr, errorTraces); //设置异常信息堆栈内容 StackTraceElement[] message = handleMess(e.getMessage(),tr); //行号初始化为0 index = 0; //堆栈信息合并 alltrace = merge(alltrace, message); alltrace = merge(alltrace, et); } //如果异常信息不为空 if(traces!=null){ traces = getKeyStackTrace(tr, traces); alltrace = merge(alltrace, traces); } //保存异常信息 throwable.setStackTrace(alltrace); tr.setThrowable(throwable); //清空 Assertion.flag = true; Assertion.errors.clear(); //输出异常信息 tr.setStatus(ITestResult.FAILURE); } } /** * 获取堆栈信息 * @param tr * @param stackTraceElements * @return */ private StackTraceElement[] getKeyStackTrace(ITestResult tr, StackTraceElement[] stackTraceElements){ List<StackTraceElement> ets = new ArrayList<>(); //循环获取信息 for (StackTraceElement stackTraceElement : stackTraceElements) { //返回测试类的堆栈信息 if(stackTraceElement.getClassName().equals(tr.getTestClass().getName())){ ets.add(stackTraceElement); index = stackTraceElement.getLineNumber(); //错误行号 } } return ets.toArray(new StackTraceElement[ets.size()]); } /** * 合并两个堆栈信息 * @param traces1 第一个 * @param traces2 * @return */ private StackTraceElement[] merge(StackTraceElement[] traces1, StackTraceElement[] traces2){ StackTraceElement[] result = Arrays.copyOf(traces1, traces1.length + traces2.length); System.arraycopy(traces2, 0, result, traces1.length, traces2.length); return result; } /** * 处理消息提示内容 * @param mess 报错信息 * @param tr 结果描述 * @return */ private StackTraceElement[] handleMess(String mess,ITestResult tr){ String message = "\n报错信息: "+mess; String method = "\n报错方法名:"+tr.getMethod().getMethodName(); String className = "\n报错类:"+tr.getTestClass().getRealClass().getSimpleName(); return new StackTraceElement[]{ new StackTraceElement(message, //内容 method, //方法名 className+"\n报错行号", //文件名 index)}; } }
四、Appium java的封装
1. Builder.java
构建器,在每个用例上都可以很方便设置app的属性package com.example.base; /** * Created by LITP on 2016/9/7. */ public class Builder { String deviceName = BaseAppium.deviceName; String platformVersion = BaseAppium.platformVersion; String path = System.getProperty("user.dir") + "/src/main/java/apps/"; String appPath = BaseAppium.appPath; String appPackage = BaseAppium.appPackage; String noReset = BaseAppium.noReset; String noSign = BaseAppium.noSign; String unicodeKeyboard = BaseAppium.unicodeKeyboard; String resetKeyboard = BaseAppium.resetKeyboard; String appActivity = BaseAppium.appActivity; public Builder setAppPath(String appPath) { this.appPath = path + appPath; return this; } public Builder setDeviceName(String deviceName) { this.deviceName = deviceName; return this; } public Builder setPlatformVersion(String platformVersion) { this.platformVersion = platformVersion; return this; } public Builder setApp(String appPath) { this.appPath = appPath; return this; } public Builder setAppPackage(String appPackage) { this.appPackage = appPackage; return this; } public Builder setNoReset(String noReset) { this.noReset = noReset; return this; } public Builder setNoSign(String noSign) { this.noSign = noSign; return this; } public Builder setUnicodeKeyboard(String unicodeKeyboard) { this.unicodeKeyboard = unicodeKeyboard; return this; } public Builder setResetKeyboard(String resetKeyboard) { this.resetKeyboard = resetKeyboard; return this; } public Builder setAppActivity(String appActivity) { this.appActivity = appActivity; return this; } public BaseAppium build() { return new BaseAppium(this); } }
2. BaseAppium.java
父类,里面封装了一堆方法,只管用,传id、name那些就行了。当然这只是一部分,仅供参考,可以自己修改添加。这个封装没有利用po模式,仅供参考,接下来的文章继续优化封装。package com.example.base; import org.apache.http.util.TextUtils; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.remote.DesiredCapabilities; import org.testng.Assert; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeSuite; import org.testng.annotations.Listeners; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.concurrent.TimeUnit; import io.appium.java_client.MultiTouchAction; import io.appium.java_client.TouchAction; import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.android.AndroidElement; /** * Created by LITP on 2016/9/7. */ @Listeners({com.example.base.AssertionListener.class}) public class BaseAppium { //调试设备名字 public static String deviceName = "minote"; //调试设备系统版本 public static String platformVersion = "4.4.2"; //app路径 public static String appPath = System.getProperty("user.dir") + "/src/main/java/apps/shouhu2.2.3.apk"; //包名 public static String appPackage = "com.minstone.mdoctor"; //是否需要重新安装 public static String noReset = "True"; //是否不重新签名 public static String noSign = "True"; //是否使用unicode输入法,真是支持中文 public static String unicodeKeyboard = "True"; //是否祸福默认呢输入法 public static String resetKeyboard = "True"; //要启动的Activity public static String appActivity = appPackage + ".activity.login.WelcomeActivity"; public AndroidDriver<AndroidElement> driver = null; //单个触摸操作类 TouchAction touchAction; //多个触摸操作时间 MultiTouchAction multiTouchAction; private static int WAIT_TIME = 10; //默认的等待控件时间 private static int SWIPE_DEFAULT_PERCENT = 5; //默认滑动比例 //构造方法 public BaseAppium() { this(new Builder()); } public BaseAppium(Builder builder) { print("基类初始化!"); appActivity = builder.appActivity; appPackage = builder.appPackage; appPath = builder.appPath; deviceName = builder.deviceName; noReset = builder.noReset; noSign = builder.noSign; unicodeKeyboard = builder.unicodeKeyboard; resetKeyboard = builder.resetKeyboard; } /** * appium启动参数 * * @throws MalformedURLException */ @BeforeSuite public void beforeSuite() throws MalformedURLException { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability("deviceName", deviceName); capabilities.setCapability("platformVersion", platformVersion); capabilities.setCapability("app", new File(appPath).getAbsolutePath()); capabilities.setCapability("appPackage", appPackage); //支持中文 capabilities.setCapability("unicodeKeyboard", unicodeKeyboard); //运行完毕之后,变回系统的输入法 capabilities.setCapability("resetKeyboard", resetKeyboard); //不重复安装 capabilities.setCapability("noReset", noReset); //不重新签名 capabilities.setCapability("noSign", noSign); //打开的activity capabilities.setCapability("appActivity", appActivity); //启动Driver driver = new AndroidDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); } @AfterTest public void afterTest() { //结束这次测试 driver.quit(); } public boolean isIdElementExist(String id) { return isIdElementExist(id, false); } /** * 根据id判断当前界面是否存在并显示这个控件 * * @param id 要查找的id * @param isShow 是否判断控件显示 * @return 返回对应的控件 */ public boolean isIdElementExist(String id, boolean isShow) { AndroidElement ae; try { if (driver != null) { ae = driver.findElementById(appPackage + ":id/" + id); if (isShow) { return ae.isDisplayed(); } else { return ae != null; } } else { print("driver为空"); } } catch (NoSuchElementException e) { print("找不到控件" + e.getMessage()); } return false; } /** * 选择当前界面的有这个文字的控件 * * @param name * @param hasShow 是否显示 * @return */ public boolean isNameElementExist(String name, boolean hasShow) { try { AndroidElement ae = driver.findElement(By.name(name)); if (hasShow) { return ae.isDisplayed(); } else return ae != null; } catch (NoSuchElementException e) { return false; } } public boolean isNameElementExist(String name) { return isNameElementExist(name, false); } /** * 判断控件时候存在 * * @param by By * @param timeout 等待的事件 * @return */ public boolean isElementExist(By by, int timeout) { try { waitAuto(by, timeout); return true; } catch (Exception e) { return false; } } /** * 根据id获取当前界面的一个控件 * * @param id 要查找的id * @return 返回对应的控件 */ public AndroidElement findById(String id) { try { if (driver != null) { return driver.findElementById(appPackage + ":id/" + id); } else { print("driver为空"); } } catch (NoSuchElementException e) { print("找不到控件" + e.getMessage()); } return null; } /** * 选择当前界面的有这个文字的控件 * * @param name 内容 * @return 找到的控件 */ public AndroidElement findByName(String name) { return driver.findElement(By.name(name)); } /** * 根据id获取当前界面的一个控件 * * @param name 要查找的控件的类名 * @return 返回对应的控件 */ public AndroidElement findByClassName(String name) { try { if (driver != null) { return driver.findElementByClassName(name); } else { print("dricer为空"); } } catch (NoSuchElementException e) { print("找不到控件" + e.getMessage()); } return null; } /** * 打印字符 * * @param str 要打印的字符 */ public <T> void print(T str) { if (!TextUtils.isEmpty(String.valueOf(str))) { System.out.println(str); } else { System.out.println("输出了空字符"); } } /** * Click点击空格键 * * @param ae 要点击的控件 * @return 返回是否点击 */ public boolean clickView(AndroidElement ae) { return clickView(ae, ""); } /** * Click点击控件 * * @param ae 控件 * @param str 控件的文字描述,供错误时候输出 * @return 返回是否存在控件 */ public boolean clickView(AndroidElement ae, String str) { if (ae != null) { ae.click(); return true; } else { print(str + "为空,点击错误"); } return false; } /** * Click点击指定id的View * * @param id 要点击的控件的id * @return 点击了返回真 */ public boolean clickView(String id) { AndroidElement ae = findById(id); if (ae != null) { ae.click(); return true; } else { print(id + "为空,点击错误"); } return false; } /** * 线程休眠秒数,单位秒 * * @param s 要休眠的秒数 */ public void sleep(long s) throws InterruptedException { Thread.sleep(s); } /** * 获取触摸实例 * * @return */ public TouchAction getTouch() { if (driver == null) { print("单点触摸时候driver为空"); return null; } else { if (touchAction == null) { return new TouchAction(driver); } else { return touchAction; } } } /** * 获取多点触摸实例 * * @return */ public MultiTouchAction getMultiTouch() { if (driver == null) { print("多点触摸时候driver为空"); return null; } else { if (multiTouchAction == null) { return new MultiTouchAction(driver); } else { return multiTouchAction; } } } /** * 往控件输入字符串 * * @param ae 要输入的控件 * @param str 要输入的字符串 */ public void input(AndroidElement ae, String str) { if (ae == null) { print("控件为空,输入内容失败:" + str); } else { ae.sendKeys(str); } } public void swipeToUp(int during){ swipeToUp(during,SWIPE_DEFAULT_PERCENT); } /** * 向上滑动, * * @param during */ public void swipeToUp(int during,int percent) { int width = getScreenWidth(); int height = getScreenHeight(); driver.swipe(width / 2, height * (percent - 1) / percent, width / 2, height / percent, during); } public void swipeToDown(int during){ swipeToDown(during,SWIPE_DEFAULT_PERCENT); } /** * 向下滑动, * * @param during 滑动时间 */ public void swipeToDown(int during,int percent) { int width = getScreenWidth(); int height = getScreenHeight(); driver.swipe(width / 2, height / percent, width / 2, height * (percent - 1) / percent, during); } public void swipeToLeft(int during){ swipeToLeft(during,SWIPE_DEFAULT_PERCENT); } /** * 向左滑动, * * @param during 滑动时间 * @param percent 位置的百分比,2-10, 例如3就是 从2/3滑到1/3 */ public void swipeToLeft(int during, int percent) { int width = getScreenWidth(); int height = getScreenHeight(); driver.swipe(width * (percent - 1) / percent, height / 2, width / percent, height / 2, during); } public void swipeToRight(int during) { swipeToRight(during, SWIPE_DEFAULT_PERCENT); } /** * 向右滑动, * * @param during 滑动时间 * @param percent 位置的百分比,2-10, 例如3就是 从1/3滑到2/3 */ public void swipeToRight(int during, int percent) { int width = getScreenWidth(); int height = getScreenHeight(); driver.swipe(width / percent, height / 2, width * (percent - 1) / percent, height / 2, during); } /** * 显示等待,等待Id对应的控件出现time秒,一出现马上返回,time秒不出现也返回 */ public AndroidElement waitAuto(By by, int time) { try { return new AndroidDriverWait(driver, time * 1000) .until(new ExpectedCondition<AndroidElement>() { @Override public AndroidElement apply(AndroidDriver androidDriver) { return (AndroidElement) androidDriver.findElement(by); } }); } catch (TimeoutException e) { Assert.fail("查找元素超时!! " + time + " 秒之后还没找到元素 [" + by.toString() + "]", e); return null; } } public AndroidElement waitAutoById(String id) { return waitAutoById(id, WAIT_TIME); } public AndroidElement waitAutoById(String id, int time) { return waitAuto(By.id(id), time); } public AndroidElement waitAutoByName(String name) { return waitAutoByName(name, WAIT_TIME); } public AndroidElement waitAutoByName(String name, int time) { return waitAuto(By.name(name), time); } public AndroidElement waitAutoByXp(String xPath) { return waitAutoByXp(xPath, WAIT_TIME); } public AndroidElement waitAutoByXp(String xPath, int time) { return waitAuto(By.xpath(xPath), time); } public void waitAuto() { waitAuto(WAIT_TIME); } /** * ,隐式等待,如果在指定时间内还是找不到下个元素则会报错停止脚本 * 全局设定的,find控件找不到就会按照这个事件来等待 * * @param time 要等待的时间 */ public void waitAuto(int time) { driver.manage().timeouts().implicitlyWait(time, TimeUnit.SECONDS); } /** * 打开Activity * * @param activityName activity的名字 */ public void startActivity(String activityName) { driver.startActivity(appPackage, activityName); } /** * 获取当前的activity,返回文件名 * * @return */ public String getCurrActivity() { String str = driver.currentActivity(); return str.substring(str.lastIndexOf(".") + 1); } /** * 获取当前界面的所有EditText,并依次输入内容 * * @param str 要输入的数组 */ public void inputManyText(String... str) { List<AndroidElement> textFieldsList = driver.findElementsByClassName("android.widget.EditText"); for (int i = 0; i < str.length; i++) { textFieldsList.get(i).sendKeys(str[i]); //textFieldsList.get(i).setValue(str[i]); } } /** * 点击某个控件 * * @param ae 要点击的控件 */ public void press(AndroidElement ae) { try { getTouch().tap(ae).perform(); } catch (Exception e) { print("tab点击元素错误" + e.getMessage()); e.printStackTrace(); } } /** * 点击某个坐标 * * @param x * @param y */ public void press(int x, int y) { try { getTouch().tap(x, y).perform(); } catch (Exception e) { print("tab点击元素错误" + e.getMessage()); e.printStackTrace(); } } /** * 长按某个控件 * * @param ae 要点击的控件 */ public void longPress(AndroidElement ae) { try { getTouch().longPress(ae).release().perform(); } catch (Exception e) { print("长按点击元素错误" + e.getMessage()); e.printStackTrace(); } } /** * 长按某个坐标 * * @param x * @param y */ public void longPress(int x, int y) { try { getTouch().longPress(x, y).release().perform(); } catch (Exception e) { print("长按点击元素错误" + e.getMessage()); e.printStackTrace(); } } /** * 在控件上滑动 * * @param element 要滑动的控件 * @param direction 方向,事件不设置默认1秒 */ public void swipOnElement(AndroidElement element, String direction) { swipOnElement(element, direction, 1000); //不设置时间就为2秒 } /** * 在某一个控件上滑动 * * @param element 在那个元素上滑动 * @param direction 方向,UP DOWN LEFT RIGHT */ public void swipOnElement(AndroidElement element, String direction, int duration) { //获取元素的起初xy,在左上角 int x = element.getLocation().getX(); int y = element.getLocation().getY(); //获取元素的宽高 int width = element.getSize().getWidth(); int height = element.getSize().getHeight(); switch (direction) { case "UP": int startX = x + width / 2; //在4/5的底部的中间向上滑动 driver.swipe(startX, y + height * 4 / 5, startX, y + height / 5, duration); break; case "DOWN": startX = x + width / 2; //在4/5的底部的中间向上滑动 driver.swipe(startX, y + height / 5, startX, y + height * 4 / 5, duration); break; case "LEFT": int startY = y + width / 2; driver.swipe(x + width * 4 / 5, startY, x + width / 5, startY, duration); break; case "RIGHT": startY = y + width / 2; driver.swipe(x + width / 5, startY, x + width * 4 / 5, startY, duration); break; } } /** * 在某个方向上滑动 * * @param direction 方向,UP DOWN LEFT RIGHT * @param duration 持续时间 */ public void swip(String direction, int duration) { switch (direction) { case "UP": swipeToUp(duration); break; case "DOWN": swipeToDown(duration); break; case "LEFT": swipeToLeft(duration); break; case "RIGHT": swipeToRight(duration); break; } } /** * 在指定次数的条件下,某个方向滑动,直到这个元素出现 * * @param by 控件 * @param direction 方向,UP DOWN LEFT RIGHT * @param duration 滑动一次持续时间 * @param maxSwipNum 最大滑动次数 */ public void swipUtilElementAppear(By by, String direction, int duration, int maxSwipNum) { int i = maxSwipNum; Boolean flag = true; while (flag) { try { if (i <= 0) { flag = false; } driver.findElement(by); flag = false; } catch (Exception e) { i--; swip(direction, duration); } } } /** * 在某个方向滑动直到这个元素出现 * * @param by 控件 * @param direction 方向,UP DOWN LEFT RIGHT * @param duration 滑动一次持续时间 */ public void swipUtilElementAppear(By by, String direction, int duration) { Boolean flag = true; while (flag) { try { driver.findElement(by); flag = false; } catch (Exception e) { swip(direction, duration); } } } /** * 获取屏幕的宽高 * * @return 返回宽高的数组 */ public int[] getScreen() { int width = driver.manage().window().getSize().getWidth(); int height = driver.manage().window().getSize().getHeight(); return new int[]{width, height}; } /** * 获取屏幕宽度 * * @return */ public int getScreenWidth() { return driver.manage().window().getSize().getWidth(); } /** * 获取屏幕高度 * * @return */ public int getScreenHeight() { return driver.manage().window().getSize().getHeight(); } /** * 逐字删除编辑框中的文字 * @param element 文本框架控件 */ public void clearText(AndroidElement element){ String text = element.getText(); //跳到最后 driver.pressKeyCode(KEYCODE_MOVE_END); for (int i = 0; i < text.length(); i ++){ //循环后退删除 driver.pressKeyCode(BACKSPACE); } } }
相关文章推荐
- [Android测试] AS+Appium+Java+Win 自动化测试之六 Appium的Java测试脚本封装
- [Android测试] AS+Appium+Java+Win 自动化测试之七: 写脚本测试自己的app
- [Android测试] AS+Appium+Java+Win 自动化测试之五:脚本重点技术
- [Android测试] AS+Appium+Java+Win 自动化测试之十:testng多设备并行测试实例封装
- [Android测试] AS+Appium+Java+Win 自动化测试之八:使用PageObject模式和重封装
- [Android测试] AS+Appium+Java+Win 自动化测试之四: 单元测试框架和TestNg
- [Android测试] AS+Appium+Java+Win 自动化测试之九:PO模式的实例与ReportNg测试报告
- AS+Appium+Java+Win 自动化测试之八:使用PageObject模式和重封装
- [Android测试] Android Studio+Appium+Java+windows 自动化测试之一: 自动化测试理解
- [Android测试] Android Studio+Appium+Java+Windows 自动化测试之二:Appium环境安装搭建
- 自动化测试(python))——解决appium每次测试脚本都要安装unlock和settings两个apk问题的方法
- [Android测试] Appium的Java-client库api
- Saucelabs+Java+TestNG+Appium+Maven+Git+Jenkins+ReportNG for Android 自动化测试
- Android 全自动js脚本测试 Appium教程——appium环境搭建(一)
- Android自动化测试 - MonkeyRunner(二) 锤子便签测试脚本
- Appium 自动化测试(3)-- 脚本开发:官方demo演示 android_contacts.py
- Appium在Android手机执行测试脚本
- Android Studio下使用JAVA+Appium进行自动化测试及简单封装
- 在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务
- 在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务