自动化工具之Appium工具简单介绍

背景

自动化,性能测试,接口测试,开发平台等工作,到底测试的价值在哪里,其实价值来源不断充实与为大众服务,今天简单介绍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)\Java\jdk1.8.0_181

%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

CLASSPATH:

.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.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">&times;</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)。上德若谷;大白若辱;广德若不足;建德若偷;质真若渝。大方无隅;大器晚成;大音希声;大象无形;道隐无名。夫唯道,善贷且成。

本文分享自微信公众号 - 7DGroup(Zee_7DGroup)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ImportSource

类图画法?这样记

很多新手甚至老手有时候都无法徒手画类图,其中原因可能就是关系线太难记。下面是我总结的类图UML中的概念和Java中的映射,以及对应的关系线。

8410
来自专栏用户5521492的专栏

单例模式,没你想的简单

来 源:http://www.tekbroaden.com/singleton-java.html

8520
来自专栏TopCoder

java lambda 容易掉进的2个坑

这是为什么呢?因为默认情况下,也就是Collectors.toMap(k -> k, v -> v)未指定BinaryOperator<U> mergeFunc...

9420
来自专栏TopCoder

深入浅出动态代理

代理模式是为了提供额外或者不同的操作,而插入代替”实际对象”的对象,即代理类,针对代理类的调用操作,都会涉及到与”实际对象”的通信,代理类起到中间人的作用。Ja...

6420
来自专栏TopCoder

java lambda 原理分析

以上代码就会产生一个Application$1.class文件和一个lambda$main$0的方法。既然lambda实现不是内部类,那么在lambda中thi...

8130
来自专栏GitHubDaily

5 门正在奋力崛起的编程语言

在软件项目与具体实现层面,我们需要考虑众多具体因素。但无论从哪种角度出发,技术栈的选择永远是决定项目成功与否的核心因素之一。根据您的实际应用需求、站点或者产品设...

7920
来自专栏TopCoder

Java常见几种动态代理的对比

•JDK动态代理:运行期动态的创建代理类,只支持接口;•ASM:一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。不过ASM在创建cl...

9330
来自专栏码力全开

[译]JS 模块化历史简介

对于 JavaScript 来说,模块化是一个相对现代的概念,这篇文章会带你在 JavaScript 的世界里快速浏览模块化的历史进程~

9320
来自专栏TopCoder

从Redis异步到反应式架构

说到Redis的通信,我们都知道Redis基于RESP(Redis Serialization Protocol)协议来通信,并且通信方式是停等模型,也就说一次...

6720
来自专栏TopCoder

深入浅出Java反射

反射,它就像是一种魔法,引入运行时自省能力,赋予了 Java 语言令人意外的活力,通过运行时操作元数据或对象,Java 可以灵活地操作运行时才能确定的信息。

9320

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励