自动化工具之Appium工具简单介绍
- 2019 年 11 月 25 日
- 筆記
背景
自动化,性能测试,接口测试,开发平台等工作,到底测试的价值在哪里,其实价值来源不断充实与为大众服务,今天简单介绍ui小工具appium攻击。
简单介绍
Appium 是一个自动化测试开源工具,支持 iOS 平台和 Android 平台上的原生应用,web 应用和混合应用。“移动原生应用”是指那些用 iOS SDK 或者 Android SDK 写的应用。所谓的“移动web 应用”是指使用移动浏览器访问的应用(Appium 支持 iOS 上的 Safari 和 Android 上的 Chrome)。所谓的“混合应用”是指原生代码封装网页视图——原生代码和 web 内容交互。Appium既能在window安装也能在mac上安装,但是wind上只能跑安卓设备,Mac上能跑安卓与IOS两个设备。 Guihub:You can write tests with your favorite dev tools using any WebDriver-compatible language such as Java, Objective-C, JavaScript (Node), PHP, Python, Ruby, C#, Clojure, or Perl with the Selenium WebDriver API and language-specific client libraries.
Appium源码地址:https://github.com/appium/appium
架构图
来自网络

UI自动收益
任何UI自动测试都不能完部替代人工测试,收益率高不高看部门怎么使用任何工具使用都是两方看怎么使用,如果有重复的工作每次需要人工去回归,建议使用自动化去回归,部门大家都用自动使用,会让大家的心信提高因为每次都机会使用自己写的脚本去验证自己重复工作。
脚本维护成本真的高吗?大家都说成本高,自己是否真的维护过,写过脚本?如果没有写过,没有维护过,没有发言权。只有自己用了才知道是否高。
内容概要
今天咱们使用windos搭建appium自动化,使用java语言做脚本语言,内容会简单介绍安装,定位,使用还有简单框架跑起来,为了节约大家时间先告诉大家本文文章主要内容是什么,这样方便是否选择看下去。希望对没有做自动化的一点启示。
- 环境安装 Android
- 常用操作
- 元素定位(原生)
- 简单java demo使用
- 简单框架设计
- 报告二次封装
环境安装
桌面版本安装
打开下面链接选择版本为exe进行下载:
https://github.com/appium/appium-desktop/releases


下载安装后

点击启动:

DOS命令安装
安装jdk
下载:
https://www.oracle.com/technetwork/java/javase/downloads/index.html
配置环境变量:如果不会配置自己百度查询桌面配置
JAVA_HOME:
JAVA_HOME=C:Program Files (x86)Javajdk1.8.0_181 %JAVA_HOME%bin;%JAVA_HOME%jrebin; CLASSPATH: .;%JAVA_HOME%lib;%JAVA_HOME%libtools.jar
java -version验证:
java version "1.8.0_181" Java(TM) SE Runtime Environment (build 1.8.0_181-b13) Java HotSpot(TM) Client VM (build 25.181-b13, mixed mode, sharing)
安装sdk
https://android-sdk.en.softonic.com/
https://android-sdk.en.softonic.com/download
配置环境变量:
ANDROID_HOME C:Program Files (x86)android-sdk-windows Path: ;%ANDROID_HOME%tools;%ANDROID_HOME%platform-tools
下载node:
http://nodejs.cn/download/
安装appium
npm install -g appium
如果上面下载比较慢可以使用如下命名:
cnpm安装: npm install -g cnpm --registry=https://registry.npm.taobao.org cnpm install -g appium --no-cache cnpm i appium-doctor appium -v
安装验证环境命令:
appium-doctor
执行命令验证是否成功:

Appium版本检查与运行显示:

注意:如果上面环境没有配置,请百度自己查询
常用操作
点击
click()
输入
sendKeys(CharSequence... keysToSend);
清除
clear()
长按
/** * 购物车商品图片 * 长按 * @param driver */ public void cartSingleProductImage(AndroidDriver<AndroidElement> driver, String coordinate) { WaitUtil.waitWebElement(driver, getByLocator.getLocatorApp(coordinate), "长按购物车商品图片-弹出收藏与删除浮层"); element = driver.findElement(getByLocator.getLocatorApp(coordinate)); int x = element.getLocation().getX(); int y = element.getLocation().getY(); TouchAction action = new TouchAction(driver); //长按 action.longPress(PointOption.point(x, y)).release().perform();}
滑动
WebElement webElement = null; try { driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS); webElement = driver.findElementByAndroidUIAutomator( "new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text("See more details"))"); } catch (Exception e) { e.printStackTrace(); }
上下左右滑动
最简单的下滑命令行:
uuid表示手机设备号
adb -s " + uuid + " shell input touchscreen swipe 400 800 400 400
/** * 滑动方法 * * @param driver * @param direction up、down、left、right */ static Duration duration = Duration.ofSeconds(1); public static void swipe(AndroidDriver<AndroidElement> driver, String direction) { switch (direction.toLowerCase()) { case "up": SwipeUp(driver); break; case "down": SwipeDown(driver); break; case "left": SwipeLeft(driver); break; case "right": SwipeRight(driver); break; default: System.out.println("方向参数不对,只能是up、down、left、right"); break; } } /** * 上滑 * * @param driver */ public static void SwipeUp(AndroidDriver<AndroidElement> driver) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Dimension size = driver.manage().window().getSize(); int height = size.height; int width = size.width; new TouchAction(driver).longPress(PointOption.point(width / 2, 100)) .moveTo(PointOption.point(width / 2, height - 100)).release() .perform(); } /** * 下滑 * * @param driver */ public static void SwipeDown(AndroidDriver<AndroidElement> driver) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Dimension size = driver.manage().window().getSize(); int height = size.height; int width = size.width; new TouchAction(driver) .longPress(PointOption.point(width / 2, height - 100)) .moveTo(PointOption.point(width / 2, 100)).release().perform(); } /** * 左滑 * * @param driver */ public static void SwipeLeft(AndroidDriver<AndroidElement> driver) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Dimension size = driver.manage().window().getSize(); int height = size.height; int width = size.width; new TouchAction(driver) .longPress(PointOption.point(width - 100, height / 2)) .moveTo(PointOption.point(100, height / 2)).release().perform(); } /** * 右滑 * * @param driver */ public static void SwipeRight(AndroidDriver<AndroidElement> driver) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Dimension size = driver.manage().window().getSize(); int height = size.height; int width = size.width; new TouchAction(driver).longPress(PointOption.point(100, height / 2)) .moveTo(PointOption.point(width - 100, height / 2)).release() .perform(); }
获取属性
getAttribute()
获取文本
getText()
获取资源
getPageSource()
元素定位
两种方式:一种使用skd中的【uiautomatorviewer.bat】进行元素定位
打开:


双击uiautomatorviewer.bat即可弹出:

在操作上面之前需要链接手机或者链接模拟器并操作命令显示:adb devices
如果是模拟器需要先链接:adb connect 127.0.0.1:62001这样再次链接

模拟器链接显示:

点击sdk中的【uiautomatorviewer.bat】


链接成功显示:

上面操作说明:
鼠标点击某个控件就会提示该控件可操作的相应内容:

说明:
其实在做移动端自动化测试,定位方式很少基本就是id/name/xpath/坐标等定位方式:
定位方式
Id定位:

driver.findElement(By.id("xxxxxx")).click();
name定位

driver.findElement(By.name("xxxxxx")).click();
xpath定位
xpath定位是最常用的一种方式,可以去学习下xpath语法:https://www.w3school.com.cn/xpath/xpath_syntax.asp 但是网上也有大牛做一个插件,做ui自动化可直接使用:https://github.com/lazytestteam/lazyuiautomatorviewer 大家下载后替换sdk中的uiautomatorviewer.jar就可使用,点击 uiautomatorviewer.bat再次弹出如下:

driver.findElement(By.xpath("xxxxxx")).click();
第二种定位方式:
目前这中方式是可以定位h5页面操作
启动:

点击:

再弹出对话中输入:

在下面选项框中输入:
需要获取appPackage与appActivity
使用命令:
aapt d badging pinduoduov4.76.0_downcc.com.apk |findstr "package launchable-activity"
获取结果:

{ "platformName": "Android", "deviceName": "127.0.0.1:62001", "appPackage": "com.xunmeng.pinduoduo", "appActivity": "com.xunmeng.pinduoduo.ui.activity.MainFrameActivity" }
点击启动


显示正在启动

启动完毕显示:

启动完毕,剩下的就是常用与其他操作一样

简单java->demo
import io.appium.java_client.AppiumDriver; import io.appium.java_client.TouchAction; import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.android.AndroidElement; import io.appium.java_client.android.AndroidKeyCode; import io.appium.java_client.functions.ExpectedCondition; import io.appium.java_client.remote.AndroidMobileCapabilityType; import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.touch.LongPressOptions; import io.appium.java_client.touch.WaitOptions; import io.appium.java_client.touch.offset.PointOption; import org.apache.commons.io.FileUtils; import org.openqa.selenium.*; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * @author liwen * @Title: Bases * @Description: 安装初始化类 * @date 2019/11/20 / 22:34 */ public class DriverBase { public static AndroidDriver<AndroidElement> driver; /** * @param port :服务器启动的端口号,系统自动获取 * @param udid :手机设备号:系统自动化获取 * @param apk :自动化运行的APK包,系统会根据该地址获取包名与actiber * @param flag :true 卸掉有重新安装与运行后自动化卸掉包。false 直接安装运行 * @return */ public static AndroidDriver<AndroidElement> initDriver(String port, String udid, String apk, boolean flag) { ArrayList<String> packAct = OperationalCmd.getPackAct(apk); // File app = new File(".\apk\20171026.apk"); DesiredCapabilities caps = new DesiredCapabilities(); //自动安装 if (flag) { caps.setCapability(MobileCapabilityType.APP, apk); //结束后会卸载程序 caps.setCapability(MobileCapabilityType.FULL_RESET, AndroidCapabilityType.FULL_RESET); } caps.setCapability(AndroidMobileCapabilityType.APPLICATION_NAME, udid); //PLATFORM_NAME: 平台名称 caps.setCapability(AndroidMobileCapabilityType.PLATFORM_NAME, AndroidCapabilityType.PLATFORM_NAME); // UDID:设置操作手机的唯一标识,android手机可以通过adb devices查看 caps.setCapability(MobileCapabilityType.DEVICE_NAME, udid); //NEW_COMMAND_TIMEOUT: appium server和脚本之间的 session超时时间 caps.setCapability(AndroidCapabilityType.NEW_COMMAND_TIMEOUT, AndroidCapabilityType.NEW_COMMAND_TIMEOUT); //APP_PACKAG:Android应用的包名 caps.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, packAct.get(0)); //APP_ACTIVITY :启动app的起始activity caps.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, packAct.get(1)); //UNICODE_KEYBOARD:1、中文输入不支持,2、不用它键盘会弹出来,说不定会影响下一步操作.需要注意设置后,需要将手机的输入法进行修改 caps.setCapability(AndroidMobileCapabilityType.UNICODE_KEYBOARD, AndroidCapabilityType.UNICODE_KEY_BOARD); //Reset_KEYBOARD:是否重置输入法 caps.setCapability(AndroidMobileCapabilityType.RESET_KEYBOARD, AndroidCapabilityType.RESET_KEY_BOARD); //NO_SIGN:跳过检查和对应用进行 debug 签名的 caps.setCapability(AndroidMobileCapabilityType.NO_SIGN, AndroidCapabilityType.NO_SIGN); try { //appium测试服务的地址 String serverUrl = "http://127.0.0.1"; driver = new AndroidDriver<>(new URL(serverUrl + ":" + port + "/wd/hub"), caps); } catch (MalformedURLException e) { e.printStackTrace(); } return driver; } }
AndroidCapabilityType
import java.io.File; /** * @author liwen * @Title: AndroidCapabilityType * @Description:功能配置类 * @date 2019/11/20 / 22:01 */ public class AndroidCapabilityType { private AndroidCapabilityType() { } public static final boolean NO_SIGN = true; public static final boolean UNICODE_KEY_BOARD = true; public static final boolean RESET_KEY_BOARD = true; /** * waitElement 时的最长等待时间 */ public static final String NEW_COMMAND_TIMEOUT = "600"; public static final String PLATFORM_NAME = "Android"; public static final boolean FULL_RESET = true; /** * 向上小滑动一步 */ public static final String APP_UP_SWIPE = "adb shell input touchscreen swipe 400 800 400 300"; public static final String APP_GET_PACK_ACTIVITY = "aapt d badging pathapk |findstr "package launchable-activity""; /** * 重启应用 */ public static final String RESTAPK = "adb -s 127.0.0.1 shell am start -n WelcomeActivityPama"; /**adb*/ /** * 根据包名得到进程 */ public static final String GETAPPPACKAGEPID = "adb shell ps | grep "; /** * 打开指定app */ public static final String OPEN_APP = "shell am start -n packagename activity"; /** * 本地存储截屏图片的目录,(注意配置时的格式) */ public static final String LOCAL_SCREEN_FILE_URL = getpathlocal(); /** * 获取目录工程路径 * * @return */ public static String getpathlocal() { File f = new File(""); String logpath = f.getAbsolutePath() + "/test-output/html/screenshots"; File file = new File(logpath); if (!file.exists()) { f.mkdirs(); } return file.toString(); } /** * 本地存储截屏图片的格式 */ public static final String LOCAL_SCREEN_FILE_FORMAT = ".png";
获取包名工具getPackAct
/** * 获取包名与 APP_ACTIVITY * * @param path * @return */ public static ArrayList<String> getPackAct(String path) { ArrayList<String> list = new ArrayList<>(); try { List<String> execute = execute(AndroidCapabilityType.APP_GET_PACK_ACTIVITY.replace("pathapk", path), true); for (String s : execute) { int i = s.indexOf("name='"); int i1 = s.indexOf("' versionCode="); if (s.contains("versionCode")) { String substring = s.substring(i + 6, i1); list.add(substring); } else { int i2 = s.indexOf("' label='"); String substring = s.substring(i + 6, i2); list.add(substring); } } } catch (Exception e) { e.printStackTrace(); } return list; }
也可以使用下面做启动:
public static AndroidDriver<?> initDriver() throws Exception { File app = new File(".\apk\20171026.apk"); DesiredCapabilities caps = new DesiredCapabilities(); caps.setCapability(MobileCapabilityType.DEVICE_NAME, "xxx"); //caps.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); //自动安装 caps.setCapability(MobileCapabilityType.AUTOMATION_NAME, "Appium"); caps.setCapability(MobileCapabilityType.UDID, "127.0.0.1:62001"); caps.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 600); //caps.setCapability(MobileCapabilityType.FULL_RESET, true); //结束后会卸载程序 caps.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "com.xunmeng.pinduoduo"); caps.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, "com.xunmeng.pinduoduo.ui.activity.MainFrameActivit"); caps.setCapability(AndroidMobileCapabilityType.UNICODE_KEYBOARD, true); caps.setCapability(AndroidMobileCapabilityType.RESET_KEYBOARD, true); caps.setCapability(AndroidMobileCapabilityType.NO_SIGN, true); driver = new AndroidDriver<>( new URL("http://127.0.0.1:4723/wd/hub"), caps); return driver; }
报告介绍
部分代码(如果需要请再群@)
/** * @author liwen * @Title: ReporterListener * @Description: 自定义报告监听类 * @date 2019/11/21 / 18:56 */ public class ReporterListener implements IReporter, ITestListener { private static final Logger log = LoggerFactory.getLogger(DriverBase.class); private static final NumberFormat DURATION_FORMAT = new DecimalFormat("#0.000"); @Override public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) { List<ITestResult> list = new LinkedList<>(); Date startDate = new Date(); Date endDate = new Date(); int TOTAL = 0; int SUCCESS = 1; int FAILED = 0; int ERROR = 0; int SKIPPED = 0; for (ISuite suite : suites) { Map<String, ISuiteResult> suiteResults = suite.getResults(); for (ISuiteResult suiteResult : suiteResults.values()) { ITestContext testContext = suiteResult.getTestContext(); startDate = startDate.getTime() > testContext.getStartDate().getTime() ? testContext.getStartDate() : startDate; if (endDate == null) { endDate = testContext.getEndDate(); } else { endDate = endDate.getTime() < testContext.getEndDate().getTime() ? testContext.getEndDate() : endDate; } IResultMap passedTests = testContext.getPassedTests(); IResultMap failedTests = testContext.getFailedTests(); IResultMap skippedTests = testContext.getSkippedTests(); IResultMap failedConfig = testContext.getFailedConfigurations(); SUCCESS += passedTests.size(); FAILED += failedTests.size(); SKIPPED += skippedTests.size(); ERROR += failedConfig.size(); list.addAll(this.listTestResult(passedTests)); list.addAll(this.listTestResult(failedTests)); list.addAll(this.listTestResult(skippedTests)); list.addAll(this.listTestResult(failedConfig)); } } /* 计算总数 */ TOTAL = SUCCESS + FAILED + SKIPPED + ERROR; this.sort(list); Map<String, TestResultCollection> collections = this.parse(list); VelocityContext context = new VelocityContext(); context.put("TOTAL", TOTAL); context.put("mobileModel", OperationalCmd.getMobileModel()); context.put("versionName", OperationalCmd.getVersionNameInfo()); context.put("SUCCESS", SUCCESS); context.put("FAILED", FAILED); context.put("ERROR", ERROR); context.put("SKIPPED", SKIPPED); context.put("startTime", ReporterListener.formatDate(startDate.getTime()) + "<--->" + ReporterListener.formatDate(endDate.getTime())); context.put("DURATION", ReporterListener.formatDuration(endDate.getTime() - startDate.getTime())); context.put("results", collections); write(context, outputDirectory); } /** * 输出模板 * * @param context * @param outputDirectory */ private void write(VelocityContext context, String outputDirectory) { if (!new File(outputDirectory).exists()) { new File(outputDirectory).mkdirs(); } //获取报告模板 File f = new File(""); String absolutePath = f.getAbsolutePath(); String fileDir = absolutePath + "/template/"; String reslutpath = outputDirectory + "/html/report" + ReporterListener.formateDate() + ".html"; File outfile = new File(reslutpath); if (!outfile.exists()) { outfile.mkdirs(); } try { //写文件 VelocityEngine ve = new VelocityEngine(); Properties p = new Properties(); p.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, fileDir); p.setProperty(Velocity.ENCODING_DEFAULT, "utf-8"); p.setProperty(Velocity.INPUT_ENCODING, "utf-8"); ve.init(p); Template t = ve.getTemplate("reportnew.vm"); //输出结果 OutputStream out = new FileOutputStream(new File(reslutpath)); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); // 转换输出 t.merge(context, writer); writer.flush(); log.info("报告位置:" + reslutpath); } catch (IOException e) { e.printStackTrace(); } } /** * 排序规则 * * @param list */ private void sort(List<ITestResult> list) { Collections.sort(list, new Comparator<ITestResult>() { @Override public int compare(ITestResult r1, ITestResult r2) { if (r1.getStatus() < r2.getStatus()) { return 1; } else { return -1; } } }); }
模板
部分代码
<h2>详情</h2> #foreach($result in $results.entrySet()) #set($item = $result.value) <table id="$result.key" class="details"> <tr> <th>测试类</th> <td colspan="4">$result.key</td> </tr> <tr> <td>TOTAL: $item.totalSize</td> <td>SUCCESS: $item.successSize</td> <td>FAILED: $item.failedSize</td> <td>ERROR: $item.errorSize</td> <td>SKIPPED: $item.skippedSize</td> </tr> <tr> <th>Status</th> <th>Method</th> <th>Description</th> <th>Duration</th> <th>Detail</th> </tr> #foreach($testResult in $item.resultList) <tr> #if($testResult.status==1) <th class="success" style="width:5em;">success </td> #elseif($testResult.status==2) <th class="failure" style="width:5em;">failure </td> #elseif($testResult.status==3) <th class="skipped" style="width:5em;">skipped </td> #end <td>$testResult.testName</td> <td>${testResult.description}</td> <td>${testResult.duration} seconds</td> <td class="detail"> ## <a class="button" href="#popup_log_${testResult.caseName}_${testResult.testName}">log</a> <button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#popup_log_${testResult.caseName}_${testResult.testName}"> log </button> <!-- 日志模态框 --> <div class="modal fade" id="popup_log_${testResult.caseName}_${testResult.testName}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel_${testResult.testName}"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="myModalLabel_${testResult.testName}">用例操作步骤</h4> </div> <div class="modal-body"> <div style="overflow: auto"> <table> <tr> <th>日志</th> <td> #foreach($msg in $testResult.twooutparam) <pre>$msg</pre> #end </td> </tr> #if($testResult.status==2) <tr> <th>异常</th> <td> <pre>$testResult.throwableTrace</pre> </td> </tr> #end </table> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> </div> </div> </div> </div> </td> </tr> #end </table> #end
启动测试类
static AndroidDriver<AndroidElement> driver; /** * 初始化运行类 * * @param udid * @param port * @throws Exception */ @BeforeClass @Parameters({"udid", "port"}) public void BeforeClass(String udid, String port) { Reporter.log("步骤1:启动appium与应用", true); LogUtil.info("---这是设备ID号-->" + udid); LogUtil.info("--这是运行端口--->" + port); //通过路径获取包名与APP_ACTIVITY String apk = "pinduoduov4.76.0_downcc.com.apk"; driver = DriverBase.initDriver(port, udid, apk, true); driver.manage().timeouts().implicitlyWait(80, TimeUnit.SECONDS); } @Test public void T001() { LogUtil.info("启动"); driver.findElement(By.id("com.xunmeng.pinduoduo:id/bo0")).click(); }
使用xml启动
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > <suite name="UI自动化" parallel="tests" thread-count="1"> <listeners> <listener class-name="appout.reporter.ReporterListener"></listener> </listeners> <test name="M6TGLMA721108530"> <parameter name="udid" value="M6TGLMA721108530"/> <parameter name="port" value="4723"/> <classes> <class name="appout.appcase.LoginTest"/> </classes> </test> </suite>
命令号启动:

这样跑xml就能得到如下结果。
效果

log弹出

工程目录

注意:
如果在启动的时候有问题,自己微调下,大概大家只是看看而已,有问题到群里问或者联系@就行会单独指导怎么使用。
总结:
使用maven建立项目,通过tesng做测试类与传参,以上简单介绍了环境部署,定位方式,启动类,报告类等方法。
在实际工作中这些远远是不够,但对与入门做参考和基础工程框架还是可以参考,如果想在运行测试类的时直接启动服务端需要参考命名怎么启动:可以参考https://www.cnblogs.com/yc-c/p/9015621.html 博客;有命令,就可以通过上面介绍的dos工具类启动服务端。
不足点:本次只是安卓端没有介绍H5怎么测试怎么定位,IOS怎么部署。
参考:
appium -a 127.0.0.1 -p 4723 -a 是指定监听的ip(也可写成 --address),后面“127.0.0.1”可以改为你需要的ip地址; -p 是指定监听的端口(也可写成 --port),也可以修改为你需要的端口;
知识点
- java基础
- Testng怎么使用
- idea怎么使用
- appium环境
- html知识
借用《道德经》41章送给大家:
上士闻道,勤而行之;中士闻道,若存若亡;下士闻道,大笑之。不笑不足以为道。故建言有之:明道若昧,进道若退,夷道若纇(lei)。上德若谷;大白若辱;广德若不足;建德若偷;质真若渝。大方无隅;大器晚成;大音希声;大象无形;道隐无名。夫唯道,善贷且成。