Appium [开源分享] 基于 Appium+java 的 APP UI 自动化测试框架 PatatiumAppUi 支持分布式并发执行用例

土豆 · 2016年12月19日 · 最后由 土豆 回复于 2016年12月29日 · 3749 次阅读

更新

20161112 增加对 Yaml 对象库的支持

20161124 增加对分布式并发执行用例的支持,可以同时将用例运行在不同的机器设备上。

简介

这是一个 AppUi 自动化测试框架,由 webdriver 中文社区创办人土豆 (本人技术笔名) 所创建,该 APP 自动化测试框架是用 java 语言编写的,基于 selenium webdriver Appium 的开源自动化测试框架,该框架结合了 testng,selenium,webdriver,Appium,jxl,jodd-http 等工具。该框架基于页面对象模型(POM)架构,实现了关键字驱动技术,数据驱动,无需掌握多少编程知识即可编写脚本,同时实现了数据与代码分离的功能:1、元素定位信息保存在对象库文件中 2、测试用例数据可以存储在 excel 中。从而实现,页面元素位置变化,无需改动脚本,只需修改对应的元素定位信息即可。
该框架实现了检查点及用例失败自动截图功能,自动生成 html 测试报告及自动发送 html 邮件测试报告功能。
目前框架还不是特别完善,还需要写一些脚本实现自动化;学习该框架需要熟悉一定的安卓 APP 和 java 基础,后续可以考虑自动编码的实现
开源地址:http://git.oschina.net/zhengshuheng/PatatiumAppUi

主要功能

1、实现关键字驱动技术,编写用例简单

2、实现数据驱动技术,减少用例代码

3、支持元素对象库管理,页面元素信息与代码分离;支持 XML,YAML 方式管理对象库

3、支持检查点、用例断言设置

4、检查点失败截图,一个检查点失败不影响用例后续执行

5、用例失败自动截图

6、用例之间依赖少,可以自由组合测试用例执行

7、支持安卓系统常用触摸操作

8、支持 APP 控件常用操作

9、用例集执行完毕自动生成简介美观的 html 报告

10、用例执行完毕自动发送详实的 html 邮件报告,可拓展为有失败用例才发送。

11、支持分布式并发执行用例的支持,可以同时将用例运行在不同的机器设备上。

Api 文档

点击查看 API 文档

环境配置

1、JDK1.8

2、IDEA\Eclipse

3、Android SDK 具体安装参考:http://www.webdriver.org/article-52-1.html

4、Maven

5、一台安卓手机或者安卓模拟器,推荐夜神安卓模拟器,下载地址:http://www.yeshen.com/

6、Appium Server 端,下载地址:http://pan.baidu.com/s/1jIxzSfO

注意事项

工程项目编码需要设置成 UTF-8 否则会出现中文乱码情况

Demo 演示

Demo 演示视频地址:http://v.youku.com/v_show/id_XMTcxMTY1MzE0NA==.html?beta&

一、创建对象库

1、通过 Android SDK 工具 uiautomatorviewer.bat 获取 app 元素定位信息,具体使用参考:http://www.webdriver.org/article-53-1.html

2、UILibrary.xml 对象库文件编写

<?xml version="1.0" encoding="UTF-8"?>
<!--整个对象库文件的根目录管理整个项目的对象-->
<map>
    <!--管理一个页面的元素webelementinput,select,textare,a,li等标签),一个page包含多个locator对象
    Pagename:page对象名字格式org.webdriver.patatiumappui.pageObject.xxxPage;最后面那位才是真正的页面名字前面的是java对象库路径
    另外注意页面名字是头个单词大写例如主页名字定义为 org.webdriver.patatiumappui.pageObject.HomePage
    Value页面对象的URL可不填
    Desc:页面对象中文描述-->
    <page pagename="org.webdriver.patatiumappui.pageObject.StartPage" value="" desc="微信APP启动首页">
        <!--管理一个页面的元素webelementinput,select,textare,a,li等标签),一个page包含多个locator对象
        Type定位方式包含id,name,class,linktext,xpath,css等定位元素的时候灵活使用一般可以统一用xpath
        代替id,name,classlinktext的定位方式
        Timeout元素加载时间有些页面元素可能要等待一段时间才能加载过来为了查找元素的稳定性需加等待时间
        Value:元素定位信息如果是id,name,classlinktext直接把网页元素对应的这些属性值写上即可如果是xpath定位方式
        需要填写正确的xpath语法格式
        Desc:元素的描述元素的中文描述信息-->
        <locator type="id" timeout="3" value="com.tencent.mm:id/c4k"  desc="登录">登录</locator>
        <locator type="id" timeout="3" value="com.tencent.mm:id/cuh"  desc="注册">注册</locator>
    </page>
    <page pagename="org.webdriver.patatiumappui.pageObject.LoginPage" value="" desc="微信App登录页面">
       <locator type="id" timeout="3" value="com.tencent.mm:id/b6c"  desc="使用其他方式登录">使用其他方式登录</locator>
        <locator type="id" timeout="3" value="com.tencent.mm:id/b5r"  desc="账号">账号输入框</locator>
        <locator type="id" timeout="3" value="com.tencent.mm:id/b5s"  desc="密码">密码输入框</locator>
        <locator type="id" timeout="3" value="com.tencent.mm:id/b5t"  desc="登录">登录按钮</locator>
        <locator type="id" timeout="3" value="com.tencent.mm:id/avt"  desc="失败提示信息确认按钮">登录失败提示信息</locator>
        <locator type="id" timeout="3" value="com.tencent.mm:id/bim"  desc="失败提示信息确认按钮">登录失败确认按钮</locator>
    </page>
</map>

YAML 对象库,UILibrary.yaml 编写

pages:
    - page:
       pageName: org.webdriver.patatiumappui.pageObject.StartPage
       value: "www.baidu.com"
       desc: "微信APP启动首页"
       locators:
          - {type: "id",timeout: "3",value: "com.tencent.mm:id/c72",desc: "登录",name: "登录"}
          - {type: "id",timeout: "3",value: "com.tencent.mm:id/c71",desc: "注册",name: "注册"}
    - page:
        pageName: org.webdriver.patatiumappui.pageObject.LoginPage
        value: ""
        desc: "微信App登录页面"
        locators:
          - {type: "id",timeout: "3" , value: "com.tencent.mm:id/b9i", desc: "使用其他方式登录",name: "使用其他方式登录"}
          - {type: "xpath",timeout: "3" ,value: "//android.widget.EditText[@text='QQ号/微信号/Email']", desc: "账号",name: "账号输入框"}
          - {type: "xpath",timeout: "3" ,value: "//android.widget.EditText[@NAF='1']", desc: "密码",name: "密码输入框"}
          - {type: "id",timeout: "3" ,value: "com.tencent.mm:id/b8z", desc: "登录",name: "登录按钮"}
          - {type: "id",timeout: "3" ,value: "com.tencent.mm:id/bl3", desc: "失败提示信息确认信息",name: "登录失败提示信息"}
          - {type: "id",timeout: "3" ,value: "com.tencent.mm:id/a_r", desc: "失败提示信息确认按钮",name: "登录失败确认按钮"}

编写完后,运行/src/main/java/org/webdriver/patatiumappui/PageObjectConfig/PageObjectAutoCodeForXml.java 或者/src/main/java/org/webdriver/patatiumappui/PageObjectConfig/PageObjectAutoCodeForYaml.java 文件生成对象库 java 代码

二、公共 action 封装实例(业务操作)

/**
 * Created by zhengshuheng on 2016/9/2 0002.
 */
public class LoginAction extends TestBaseCase {
    public  LoginAction(String username,String password) throws IOException {
        ElementAction action=new ElementAction();
        LoginPage loginPage=new LoginPage();
        action.click(loginPage.账号输入框());
        action.clear(loginPage.账号输入框());
        action.type(loginPage.账号输入框(),username);
        action.click(loginPage.密码输入框());
        action.clear(loginPage.密码输入框());
        action.type(loginPage.密码输入框(),password);
        action.sleep(1);
        action.click(loginPage.登录按钮());
    }
}

三、驱动数据来源实例

在 src/main/resources/data 下创建 loginData.xls 文件

四、测试用例编写

/**
 * Created by zhengshuheng on 2016/9/2 0002.
 */
public class LoginTest extends TestBaseCase {
    ElementAction action=new ElementAction();
    @BeforeClass
    public  void  beforeclass() throws IOException {
        StartPage startPage=new StartPage();
        action.click(startPage.登录());
        LoginPage loginPage=new LoginPage();
        action.sleep(2);
        action.click(loginPage.使用其他方式登录());
        action.sleep(2);
    }
    @Test(description = "登录测试")
    public  void login() throws IOException {
        //调用登录方法(需填写正确的用户名和密码)
        new LoginAction("655433", "gg");
        action.sleep(5);
        //设置检查点
        Assertion.VerityTextPresent("通讯录","验证是否登录成功!");
        //设置断言 。判断用例是否失败
        Assertion.VerityError();
    }
    //数据驱动案例--start
    @DataProvider(name="longinData")
    public Object[][] loginData()
    {
        //读取登录用例测试数据
        String filePath="src/main/resources/data/loginData.xls";
        //读取第一个sheet,第2行到第5行-第2到第4列之间的数据
        return ExcelReadUtil.case_data_excel(0, 1, 2, 1, 3,filePath);
    }
    @Test(description="登录失败用例:数据驱动例子",dataProvider = "longinData")
    public void loginFail (String userName,String password,String message) throws IOException, DocumentException {
        //调用登录方法
        new  LoginAction(userName,password);
        LoginPage loginPage=new LoginPage();
        action.sleep(4);
        log.info("登录失败信息:"+action.getText(loginPage.登录失败提示信息()));
        Assertion.VerityCationString(action.getText(loginPage.登录失败提示信息()),message,"验证是否出现预期的错误提示信息:"+message);
        action.click(loginPage.登录失败确认按钮());
        //设置断言
        Assertion.VerityError();
    }
}

五、Testng.xml 配置

tesng.xml 需配置 app 主包名,主类名,SDK 版本,Device Name 等信息
Device Name:在 cmd 命令下通过 adb devices 获取

App 主包名,主类名等在 Appium Server GUI 获取

App 安装包 apk 文件放在项目根目录下 apps 目录里,tesng.xml 需指定运行 apk 文件名

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite" >
    <parameter name="driverName" value="AndroidDriver" />   <!--driver驱动安卓IOS-->
    <parameter name="nodeURL" value="127.0.0.1:4723" /> <!--appium-server 地址-->
    <parameter name="appName" value="weixin_861.apk" />  <!--app包名字-->
    <parameter name="platformName" value="Android" /> <!--app运行平台:安卓IOS-->
    <parameter name="deviceName" value="127.0.0.1:62001" /> <!--手机或者虚拟机设备名字-->
    <parameter name="sdkVersion" value="6.0" /><!--安卓,IOS SDK版本-->
    <parameter name="appMainPackage" value="com.tencent.mm" /><!--app主包名-->
    <parameter name="appActivity" value="com.tencent.mm.ui.LauncherUI" /> <!--app 主类名-->
    <parameter name="UserName" value="" /> <!-- 系统登录用户名-->
    <parameter name="PassWord" value="" />  <!-- 系统登录密码-->
    <parameter name="smtpUserName" value="" />  <!-- 测试报告邮件发送smtp身份证验证-->
    <parameter name="smtpPassWord" value="" />  <!-- 测试报告邮件发送smtp身份证验证-->
    <parameter name="smtpHost" value="" />  <!-- 测试报告邮件发送smtp主机地址-->
    <parameter name="smtpPort" value="" />  <!-- 测试报告邮件发送smtp主机端口-->
    <parameter name="mailTitle" value="Webdriver中文社区-自动化测试报告" />  <!-- 测试报告邮件发送邮件标题-->
    <parameter name="logUrl" value="" />  <!-- 测试报告邮件发送用例运行日志url-->
    <parameter name="reportUrl" value="" />  <!-- 测试报告邮件发送完整测试报告url-->
    <parameter name="recipients" value="" /> <!-- 测试报告邮件发送收件人多个用,号隔开-->
    <parameter name="reportTitle" value="Webdriver中文社区-自动化测试报告" />  <!--测试报告标题-->

    <listeners><!-- 监听器设置-->
        <listener class-name="org.webdriver.patatiumappui.utils.TestListener"/>
        <listener class-name="org.webdriver.patatiumappui.utils.TestReport"/>
    </listeners>
    <test name="登录测试">
        <classes>
            <class name="LoginTest">
                <methods>
                    <include name="login"/>
                </methods>
            </class>
        </classes>
    </test>
    <test name="登录失败测试:数据驱动">
        <classes>
            <class name="LoginTest">
                <methods>
                    <include name="loginFail"/>
                </methods>
            </class>
        </classes>
    </test>
</suite> <!-- Suite -->

分布式并发执行用例,同时将用例运行在不同设备上的 testng.xml 配置如下

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite" parallel="tests" thread-count="2">
    <parameter name="driverName" value="AndroidDriver" />   <!--driver驱动安卓IOS-->
    <parameter name="nodeURL" value="127.0.0.1:4723" /> <!--appium-server 地址-->
    <parameter name="appName" value="weixin_861.apk" />  <!--app包名字-->
    <parameter name="platformName" value="Android" /> <!--app运行平台:安卓IOS-->
    <parameter name="deviceId" value="127.0.0.1:62001" /> <!--手机或者虚拟机设备Id通过adb device获取-->
    <parameter name="deviceName" value="127.0.0.1:62001" /> <!--手机或者虚拟机设备名字自定义设备名字-->
    <parameter name="sdkVersion" value="6.0" /><!--安卓,IOS SDK版本-->
    <parameter name="appMainPackage" value="com.tencent.mm" /><!--app主包名-->
    <parameter name="appActivity" value="com.tencent.mm.ui.LauncherUI" /> <!--app 主类名-->
    <parameter name="UserName" value="" /> <!-- 系统登录用户名-->
    <parameter name="PassWord" value="" />  <!-- 系统登录密码-->
    <parameter name="smtpUserName" value="" />  <!-- 测试报告邮件发送smtp身份证验证-->
    <parameter name="smtpPassWord" value="" />  <!-- 测试报告邮件发送smtp身份证验证-->
    <parameter name="smtpHost" value="" />  <!-- 测试报告邮件发送smtp主机地址-->
    <parameter name="smtpPort" value="" />  <!-- 测试报告邮件发送smtp主机端口-->
    <parameter name="mailTitle" value="Webdriver中文社区-自动化测试报告" />  <!-- 测试报告邮件发送邮件标题-->
    <parameter name="logUrl" value="" />  <!-- 测试报告邮件发送用例运行日志url-->
    <parameter name="reportUrl" value="" />  <!-- 测试报告邮件发送完整测试报告url-->
    <parameter name="recipients" value="" /> <!-- 测试报告邮件发送收件人多个用,号隔开-->
    <parameter name="reportTitle" value="Webdriver中文社区-自动化测试报告" />  <!--测试报告标题-->

    <listeners><!-- 监听器设置-->
        <listener class-name="org.webdriver.patatiumappui.utils.TestListener"/>
        <listener class-name="org.webdriver.patatiumappui.utils.TestReport"/>
    </listeners>
    <test name="登录测试1-设备1">
        <parameter name="nodeURL" value="127.0.0.1:4725" /> <!--appium-server 地址-->
        <parameter name="deviceId" value="127.0.0.1:62001" /> <!--手机或者虚拟机设备Id通过adb device获取-->
        <parameter name="deviceName" value="127.0.0.1:62001" /> <!--手机或者虚拟机设备名字自定义设备名字-->
        <classes>
            <class name="LoginTest">
                <methods>
                    <include name="login"/>
                </methods>
            </class>
        </classes>
    </test>
    <test name="登录测试2-设备2">
        <parameter name="nodeURL" value="127.0.0.1:4726" /> <!--appium-server 地址-->
        <parameter name="deviceId" value="127.0.0.1:62025" /> <!--手机或者虚拟机设备Id通过adb device获取-->
        <parameter name="deviceName" value="127.0.0.1:62025" /> <!--手机或者虚拟机设备名字自定义设备名字-->
        <classes>
            <class name="LoginTest">
                <methods>
                    <include name="login"/>
                </methods>
            </class>
        </classes>
    </test>
    <test name="登录测试3-设备3">
        <parameter name="nodeURL" value="127.0.0.1:4727" /> <!--appium-server 地址-->
        <parameter name="deviceId" value="127.0.0.1:62026" /> <!--手机或者虚拟机设备Id通过adb device获取-->
        <parameter name="deviceName" value="127.0.0.1:62026" /> <!--手机或者虚拟机设备名字自定义设备名字-->
        <classes>
            <class name="LoginTest">
                <methods>
                    <include name="login"/>
                </methods>
            </class>
        </classes>
    </test>
    <!--<test name="登录失败测试:数据驱动">-->
        <!--<classes>-->
            <!--<class name="LoginTest">-->
                <!--<methods>-->
                    <!--<include name="loginFail"/>-->
                <!--</methods>-->
            <!--</class>-->
        <!--</classes>-->
    <!--</test>-->
</suite> <!-- Suite -->

六、执行用例

IDE:在 IDE 集成开发环境下右键 testng.xml 使用 testng 运行

Maven:执行 mvn clean ;mvn test 命令

Jenkins:1、checkout 项目代码 2、指定 pom.xml 文件 3、执行 mvn clean ;mvn test 命令

七、查看测试报告及日志文件

用例执行完毕,会自动发送邮件报告及生成测试报告文件;用例失败会自动截图并将其展示在报告中,也可以将用例失败堆栈信息显示到报表中

测试报告文件生成在项目根目录下 test-out 目录下 report.html 文件

报告展示如下:点击查看在线测试报告



License

GPL V2.0

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 33 条回复 时间 点赞
土豆 #33 · 2016年12月29日 Author

#32 楼 @510408669 看下环境要求,maven 是否有安装? 这个是 maven 项目,导入的时候要选择 maven 模板,导入之后,要通过 pom.xml 下载依赖。

—— 来自 TesterHome 官方 安卓客户端

根据你说的地址下载源码,用 idea 打开后,编译不能通过,提示各种包不存在。。。

#30 楼 @JeffLiu 你这个是依赖没下载下来,java 找不到 selenium 的类。直接通过 pom.xml 就可把依赖下载下来了。

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.18.1:test (default-test) on project PatatiumAppUi: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.18.1:test failed: There was an error in the forked process
[ERROR] java.lang.NoClassDefFoundError: org/openqa/selenium/remote/RemoteWebDriver
Maven Install 这个错误怎么解决呢。

#27 楼 @potato 万分感谢,我这边是因为加了关键字驱动没有加数据驱动所以测试数据会比较单一;我昨天在开源中国上也看了你的代码,觉得你的思路对我帮助还是蛮大的(只是因为不能下载代码所以看着很不方便😅)

很好很好,感谢分享。

—— 来自 TesterHome 官方 安卓客户端

土豆 #26 · 2016年12月22日 Author

#25 楼 @haifushi 是我搞错了 github 上是传了 webui 的,跟这个是差不多的。我上面贴的地址是开源中国的 git,跟 github 一样也可以下载下来的。晚上我再把它弄 github 上去

—— 来自 TesterHome 官方 安卓客户端

#23 楼 @potato 给力👍看到上面楼主说已更新代码到 github,想学习一下数据驱动的实现,烦请留个 git 的地址,万分感谢

@pocoyo 大神这个 生成测试报告的关键代码没有开源 能否分享?
另外通过 excel 生成测试代码 据说望京某大神已经实现全套反射对应测试脚本 求大神分享

土豆 #11 · 2016年12月21日 Author

#19 楼 @haifushi YAML 解析已经搞定了,等有时间就增加个对 YAML 的支持。

土豆 #12 · 2016年12月21日 Author

#16 楼 @seveniruby 我拓展了下支持 YAML 管理对象库,解析了下 YAML,感觉 YAML 可读性可能是比 XML 高,也看个人习惯,但解析起来并没有 XML 方便,过程也跟解析 XML 差不多,主要可能是因为 java 的数据类型问题,如果是用 python 解析应该会方便很多。也不知道是不是因为我没用好

/**
     *
     * @param path 对象库文件地址
     * @param pageName pageName 页面名字
     * @return 返回locator 哈希表  locatorName:locator
     */
    public Map<String,Locator>  getLocatorMap(String path,String pageName) throws FileNotFoundException, YamlException {
          Map<String,Locator> locatorHashMap=new HashMap<>();
          YamlReader yamlReader=new YamlReader(new FileReader(path));
          Object yamlObject=yamlReader.read();
          Map yamlMap=(Map) yamlObject;
          ArrayList<HashMap<String,Object>> pages=(ArrayList<HashMap<String,Object>>)yamlMap.get("pages");
        for (int i=0;i<pages.size();i++)//遍历Page节点
        {
            HashMap<String,Object> pageNode=pages.get(i);//获取page节点
            HashMap<String,Object> pageElement=(HashMap<String,Object>)pageNode.get("page");
            if (pageElement.get("pageName").toString().equalsIgnoreCase(pageName))//判断是否需要获取的Page节点
            {
//                System.out.println(pageElement.get("desc"));
                List<HashMap<String,Object>> locators=(List<HashMap<String,Object>>)pageElement.get("locators");//获取locators列表
                for (int j=0;j<locators.size();j++)//遍历locators列表
                {
                    HashMap<String,Object> locatorNode=locators.get(j);
                    Locator locator=new Locator();
                    locator.setType(getByType(locatorNode.get("type").toString()));
                    locator.setValue(locatorNode.get("value").toString());
                    locator.setTimout(Integer.parseInt(locatorNode.get("timeout").toString()));
                    locator.setLocatorName(locatorNode.get("name").toString());
                    locatorHashMap.put(locatorNode.get("name").toString(),locator);
                }

            }else {continue;}
//            System.out.println(pageObjet);
        }
        System.out.println(locatorHashMap.get("登录").getLocalorName());
        return locatorHashMap;


    }

学习一下思路

大神,厉害

大神能开源到 github 吗,还有 yaml 格式会看上去更直观点👍

#15 楼 @potato 那看来你这个不是最新版本了

#14 楼 @xdf 代码是给人读的. 机器运行的其实是编译后的各种字节码或汇编代码. 早期有 xml 是个很大的进度. 不过主要是为了让接口协议通讯用的. 因为用的太广泛了. 又是文本化的, 也就顺便做各种配置了. 如果是用来自己手写的配置文件. yaml 或者 nginx.conf 的风格更容易被接受, 这些都有语义的信息. 能表示高阶的引用复用等特性. 不是死板的.

土豆 #15 · 2016年12月20日 Author

#13 楼 @hxbjava 之前我也有考虑过用 excel 编写用例就是通过 excel 生成测试代码,后面我直接用 Spring MVC 去搞了,就全部走数据库了,连 Testng,Excel 都用不上了,就没去弄 excel 编写用例的

#2 楼 @seveniruby xml是给机器用的, 不是给人用的 哈哈,代码是给人读的,顺便给机器运行

看了一下你的测试用例编写,你这里仍然需要在代码里面编写并用 test 标记。我目前做到的是 可以直接在 excel 上编写测试用例,然后运行,无需改动任何代码。元素封装由于我是 ios 和 android 共用所以目前仍然用的 excel。

土豆 #22 · 2016年12月20日 Author

#11 楼 @hxbjava 后期还准备集成性能监控,crash 收集 ,这个是很不错的,很多确实可以加进去,我的框架也还不是很完善,只是提供一种方便与测试代码编写的 API 风格。

早在几个月以前已经完成了框架的封装,和你相比只差在 html 的改造。后期还准备集成性能监控,crash 收集;我的是 ios 和 android 公用一套。

和我之前做的一套好像,不过是基于 uiautomator2.0 的,做的没你的完善哈!可以考虑借鉴过来。

—— 来自 TesterHome 官方 安卓客户端

土豆 #26 · 2016年12月20日 Author

#4 楼 @jaylin 目前还没有 python 版本

土豆 #27 · 2016年12月20日 Author

#5 楼 @kasi 😃 以前只是看看,没注册账号,这次注册账号了,加入你们的阵营了😀

#2 楼 @seveniruby 感谢你的建议!是我自己开发的,这个已经开源到 github 了,只是考虑到 github 是国外的服务器比较慢,所以在开源中国放了一份,yaml 确实可以考虑,之所以采用 xml 主要考虑到它的通用性。 呵呵,markdown,昨天也是编辑了几次才搞定,还是有些换行没搞好

大树过来了啊

真棒!!不知道有没有 python 版本!

厉害

是你自己开发的吗. 感觉思路有创新. 我提个小建议

  1. 开源到 github
  2. xml 是给机器用的, 不是给人用的. 可以考虑 yaml 之类的格式, 可以参考社区的 AppiumBooster
  3. markdown 里面结尾用两个空格 + 换行才能真的换行.

厉害了哥

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册