AS+Appium+Java+Win自动化测试之Appium的Java测试脚本封装(Android测试)
程序员文章站
2022-10-30 14:55:04
一、为什么需要封装?
封装的本意就是为了方便、简洁。
二、android的显式等待封装
1. androiddriverwait.java
package com.example.base;...
一、为什么需要封装?
封装的本意就是为了方便、简洁。
二、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 { 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 extends function {}
3. 使用
/** * 显示等待,等待id对应的控件出现time秒,一出现马上返回,time秒不出现也返回 */ public androidelement waitauto(by by, int time) { try { return new androiddriverwait(driver, time) .until(new expectedcondition() { @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 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 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 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("https://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 byby * @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 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() { @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 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); } } }
推荐阅读
-
Appium Python自动化测试之环境搭建的步骤
-
Appium python自动化测试系列教程之关于Android知识的讲解(三)
-
AS+Appium+Java+Win自动化测试之Appium的Java测试脚本封装(Android测试)
-
基于 Appium 的 Android UI 自动化测试
-
Java进行Appium自动化测试的实现
-
实现android应用程序自动化测试的批处理脚本
-
[android]android自动化测试八之让你的AVD无法连接网络
-
Appium python自动化测试系列教程之关于Android知识的讲解(三)
-
AS+Appium+Java+Win自动化测试之Appium的Java测试脚本封装(Android测试)
-
Java进行Appium自动化测试的实现