Appium 微信 H5 等 H5 应用自动化测试如何与 PC 端 WebUI 自动化统一入口

土豆 · 2017年03月11日 · 最后由 nmvjhd 回复于 2018年10月09日 · 2017 次阅读

时下 H5 应用很火,微信公众号,微信小程序,支付宝服务窗等,其应用跟 PC 端的 Web 应用有共同也有区别之处,但其实 H5 应用跟 pc 端的 web 应用是差不多的,其本质都是 web 应用,都是要通过浏览器或者 Webview 浏览器进行操作,只不过 H5 应用更多的是运行在手机端而不是 PC 端。由于设备端不一样,H5 应用是在手机端,没法像 PC 端的 WEB 应用那么简单,所以很多时候,可能是把 H5 划归到 APP 自动化进行管理的。既然有这么多共同之处,那么有没有办法把它封装成一种浏览器,归类到 Web 自动化测试中呢,达到跟 WEB UI 统一的操作入口?看了思寒的微信 Webview 自动化测试方法的帖子,给了我灵感,特分享出来

网上流传的 H5 自动化测试有两种方案,一种是利用 PC 端谷歌浏览器的设备 UA 模式,模拟手机浏览器,一种是利用 Appium 连接手机,进入应用切换到 Webview;一种方案是比较简单的,但是没有办法完全模拟手机浏览器的环境,所以采用第二种方法。下面说下具体的过程
一、将 apium 注册到 selenium gird
采用 Appium 的方式,Appium Driver 启动方式与 Webdriver 的启动方式略有不同,那么如何达到统一的入口呢?Selenium 有一种分布式解决方案 Selenium Grid,如果把 Appium Server 注册到 Selenium Grid hub 上就实现了统一的入口了。appium 注册到 selenium grid 的方法,网上有很多例子,但经过实验发现,有几个地方需要特别注意:
1、注册 node 节点的时候需指定 udid, 2、启动的时候要顺带启动 appium 内置的 chromedriver,3、appium 地址,grid hub 地址不能指定为 127.0.0.1,不然其他机器就没办法通过 ip 进行访问了。下面是我的 appium 注册代码:
nodeconfig 如下:

{  
    "capabilities": [  
        {  
            "browserName": "chrome",  
            "version": "5.0.1",  
            "maxInstances": 1,  
            "platform": "ANDROID",
       "udid":"127.0.0.1:62001",
       "deviceName":"127.0.0.1:62001"
        }  
    ],  
    "configuration": {  
        "cleanUpCycle": 2000,  
        "timeout":30000,  
        "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",  
    "hub":"192.168.1.103:4455/grid/register",
        "url":"http://192.168.1.103:4723/wd/hub",  
        "host": "192.168.1.103",  
        "port": 4723,  
        "maxSession": 1,  
        "register": true,  
        "registerCycle": 5000,  
        "hubPort": 4455,  
        "hubHost": "192.168.1.103"   
    }  
}  

appium 启动脚本:

appium --address 192.168.1.103 --port 4723  --bootstrap-port 4724 --chromedriver-port 8000  --session-override --nodeconfig D:\auto\appium-node\appium-node.json

二、设计 testbase(浏览器启动和关闭) 类

原理:由于微信浏览器没有像 PC 浏览器一样有网址输入口,所以需要通过文件传输助手输入自己设计的网址入口地址(类似 hao123 入口)

public class TestBaseCase {
    public static WebDriver driver;
    public static String description;
    public Log log=new Log(this.getClass().getSuperclass());
    public void  setup( String driver,String nodeURL) throws MalformedURLException {
        log.info("------------------开始执行测试---------------");
        log.info("读取xml配置:浏览器:"+driver+";gridNodeURL:"+nodeURL);
        try {
                this.driver=setRemoteDriver(driver,nodeURL);
        } catch (Exception e) {
                log.error("没有成功浏览器环境配置错误");
        }

        this.driver.manage().window().maximize();

    }

    @AfterTest
    public void tearDown() {
        try {
            this.driver.close();
            this.driver.quit();
        }catch (Exception e) {
            log.info("android driver 退出需切换会NATIVE_APP");
            AndroidDriver androidDriver=(AndroidDriver) driver;
            androidDriver.quit();
        }
    }
    private WebDriver setRemoteDriver(String browsername,String nodeURL) throws MalformedURLException
    {
        switch (browsername)
        {

            case "FirefoxDriver" :
                DesiredCapabilities capabilities=DesiredCapabilities.firefox();
                capabilities.setBrowserName("firefox");
                capabilities.setPlatform(Platform.WINDOWS);
                driver= new RemoteWebDriver(new URL(nodeURL), capabilities);
                break;
            case "ChormeDriver":
                DesiredCapabilities dcchorme=DesiredCapabilities.chrome();
                dcchorme.setBrowserName("chrome");
                dcchorme.setVersion("46.0.2490.86 m");
                dcchorme.setPlatform(Platform.WINDOWS);
                driver=new RemoteWebDriver(new URL(nodeURL), dcchorme);
                break;
            case "WeiXIN":
                DesiredCapabilities capability = new DesiredCapabilities();
                capability.setCapability("app", "");
                capability.setCapability("appPackage", "com.tencent.mm");
                capability.setCapability("appActivity", ".ui.LauncherUI");
                capability.setCapability("deviceName", "127.0.0.1:62001");
                capability.setCapability("fastReset", "false");
                capability.setCapability("fullReset", "false");
                capability.setCapability("noReset", "true");
                capability.setCapability("unicodeKeyboard", "True");
                                capability.setCapability("resetKeyboard", "True");
                //关键是加上这段
                ChromeOptions options2 = new ChromeOptions();
                options2.setExperimentalOption("androidProcess", "com.tencent.mm:tools");
                capability.setCapability(ChromeOptions.CAPABILITY, options2);
                //启动微信浏览器
                log.info("启动微信浏览器");
                 driver= new AndroidDriver(new URL(nodeURL), capability);
                driver.manage().timeouts().implicitlyWait(500, TimeUnit.MILLISECONDS);
                sleep(5);
                                log.info("点击微信搜索菜单");
                WebElement webElement=driver.findElement(By.xpath("//*[@content-desc='搜索']"));
                webElement.click();
                log.info("输入文件字符串");
                webElement=driver.findElement(By.xpath("//*[@text='搜索']"));
                webElement.click();
                webElement.clear();
                webElement.sendKeys("文件");
                sleep(4);
                log.info("点击文件传输助手");
                webElement=driver.findElement(By.xpath("//*[@text='文件传输助手']"));
                webElement.click();
                sleep(8);
                log.info("发送跳转网站的网页URL");
                   webElement= driver.findElement(By.xpath("//*[@resource-id='com.tencent.mm:id/z4']"));//不同微信版本,定位不一样
                webElement.sendKeys("http://192.168.1.103:8080/openurl/open.html");
                log.info("点击发送按钮");
                webElement=driver.findElement(By.xpath("//*[@text='发送']"));
                webElement.click();
                sleep(3);
                log.info("点击网址");
                webElement=driver.findElement(By.xpath("//*[@text='"+"http://192.168.1.103:8080/openurl/open.html"+"']"));
                webElement.click();
                  sleep(3);
                  log.info("切换到微信webView");
                               //Webdriver转AndroidDriver
                                AndroidDriver androidDriver=(AndroidDriver) driver;
                androidDriver.context("WEBVIEW_com.tencent.mm:tools");
                                 driver=androidDriver;

            case "HtmlUnitDriver":
                this.driver=new HtmlUnitDriver();
                break;
            default:
                this.driver=new FirefoxDriver();
                break;
        }
        return driver;
    }
}

三、设计网址入口网页


<html>
<head>
 <meta charset="GBK"/>
 <script src="http://cdn.static.runoob.com/libs/jquery/1.10.2/jquery.min.js">
</script>
</head>
<body style="text-align:center">
   <div>
      <input id="url" type="text"/>
      <input id="openurl" type="button" value="打开网页"/>
   </div>
   <script>
     $("#openurl").click(function(){
     var url=$("#url").val();
         location.href=url;
      }
     )
   </script>
</body>
</html>

四、设计 open 方法

//微信浏览器操作
public void openWeiXinBrowser(String url)  {
    WebElement webElement=driver.findElement(By.id("url"));
    log.info("输入网址:"+url);
    webElement.sendKeys(url);
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    webElement=driver.findElement(By.id("openurl"));
    log.info("点击打开网页");
    webElement.click();
}

五、设计截图方法

public class ScreenShot {
    public WebDriver driver;
    private String screenName;
    Log log =new Log(this.getClass());
    public void setscreenName(String screenName)
    {
        this.screenName=screenName;
    }
    public ScreenShot(WebDriver driver)
    {
        this.driver=driver;
    }
    private void takeScreenshot(String screenPath) {
        try{
            scrFile=((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        }catch (Exception e)//关键就在于此
        {
            //appium 内chrome没法截图,需用原生app下进行截图
            System.out.println("进入到了webview 截图需切换回 NATIVE_APP content");
            AndroidDriver driver2=((AndroidDriver) driver);//关键就在于此
            log.info("contextName:"+driver2.getContext());
            System.out.println(driver2.getContextHandles());
//          log.info(driver2.getContextHandles().toString());
            log.info("切换到NATIVE_APP进行app截图");
            driver2.context("NATIVE_APP");
            scrFile=((TakesScreenshot) driver2).getScreenshotAs(OutputType.FILE);
            log.info("切换回微信webviw");
            driver2.context("WEBVIEW_com.tencent.mm:tools");
        }
        try {
            Files.copy(scrFile, new File(screenPath));
            log.error("错误截图:"+screenPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public  void takeScreenshot() {
        String screenName =this.screenName+ ".jpg";
        File dir = new File("test-output\\snapshot");
        if (!dir.exists())
        {dir.mkdirs();}
        String screenPath = dir.getAbsolutePath() + "\\" + screenName;
        this.takeScreenshot(screenPath);
    }

}

到此就完成了微信浏览器入口统一,这里面的坑是 AndroidDriver 的 Contentext 方法并没有在 Webdriver 里定义,而微信截图等很多地方需要切换 Contentext 才能操作,而要达到入口统一肯定需要通过 Webdriver 接口作为浏览器的管理;这个坑在网上也没有答案,冥思苦想后终于想出了类型转换的方法,我也是佩服自己能想出接口与实现类之间类型转换的方法。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 7 条回复 时间 点赞

能否留个联系方式,请教你一下问题,或者加我微信也行 Chenjun821710

哎呀,树恒,截图实在不行就用 adb 实现了

不切 driver 可以么?

楼主我用你微信那块代码运行报如下错,不知道什么原因。chromedriver 版本换了 chromedriver=2.26, chromedriver=2.33,chromedriver=2.38 都不行

[INFO ] 2018-04-19 11:48:52,751 method:com.jnt.csair.weixin.H5Weixin.main(H5Weixin.java:41)
启动微信浏览器
Exception in thread "main" org.openqa.selenium.WebDriverException: An unknown server-side error occurred while processing the command. Original error: Failed to start Chromedriver session: An unknown server-side error occurred while processing the command. (Original error: unknown error: known package com.android.chrome does not accept activity/process
(Driver info: chromedriver=2.26.436362 (5476ec6bf7ccbada1734a0cdec7d570bb042aa30),platform=Windows NT 10.0.15063 x86_64)) (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 23.01 seconds
Build info: version: '3.11.0', revision: 'e59cfb3', time: '2018-03-11T20:33:15.31Z'
System info: host: 'JIANGNENGTING', ip: '192.168.18.254', os.name: 'Windows 10', os.arch: 'amd64', os.version: '10.0', java.version: '1.8.0_131'
Driver info: driver.version: AndroidDriver
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:214)
at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:166)
at org.openqa.selenium.remote.JsonWireProtocolResponse.lambda$new$0(JsonWireProtocolResponse.java:53)
at org.openqa.selenium.remote.JsonWireProtocolResponse.lambda$getResponseFunction$2(JsonWireProtocolResponse.java:91)
at org.openqa.selenium.remote.ProtocolHandshake.lambda$createSession$0(ProtocolHandshake.java:123)
at java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)
at java.util.Spliterators$ArraySpliterator.tryAdvance(Unknown Source)
at java.util.stream.ReferencePipeline.forEachWithCancel(Unknown Source)
at java.util.stream.AbstractPipeline.copyIntoWithCancel(Unknown Source)
at java.util.stream.AbstractPipeline.copyInto(Unknown Source)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
at java.util.stream.FindOps$FindOp.evaluateSequential(Unknown Source)
at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
at java.util.stream.ReferencePipeline.findFirst(Unknown Source)
at org.openqa.selenium.remote.ProtocolHandshake.createSession(ProtocolHandshake.java:126)
at org.openqa.selenium.remote.ProtocolHandshake.createSession(ProtocolHandshake.java:73)
at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:136)
at io.appium.java_client.remote.AppiumCommandExecutor.execute(AppiumCommandExecutor.java:129)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:545)
at io.appium.java_client.DefaultGenericMobileDriver.execute(DefaultGenericMobileDriver.java:42)
at io.appium.java_client.AppiumDriver.execute(AppiumDriver.java:1)
at io.appium.java_client.android.AndroidDriver.execute(AndroidDriver.java:1)
at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:209)
at org.openqa.selenium.remote.RemoteWebDriver.(RemoteWebDriver.java:132)
at io.appium.java_client.DefaultGenericMobileDriver.(DefaultGenericMobileDriver.java:38)
at io.appium.java_client.AppiumDriver.(AppiumDriver.java:84)
at io.appium.java_client.AppiumDriver.(AppiumDriver.java:94)
at io.appium.java_client.android.AndroidDriver.(AndroidDriver.java:79)
at com.jnt.csair.weixin.H5Weixin.main(H5Weixin.java:42)

原码如下: 有知道的兄弟们不.

import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.DesiredCapabilities;

import io.appium.java_client.android.AndroidDriver;

public class H5Weixin {

private static Logger logger = Logger.getLogger(H5Weixin.class);

public static void main(String[] args) throws InterruptedException, MalformedURLException {
// TODO Auto-generated method stub
AndroidDriver driver = null;
DesiredCapabilities capability = null;

capability = new DesiredCapabilities();
capability.setCapability("app", "");
capability.setCapability("appPackage", "com.tencent.mm");
capability.setCapability("appActivity", ".ui.LauncherUI");
capability.setCapability("deviceName", "3bf6f87e");
capability.setCapability("fastReset", "false");
capability.setCapability("fullReset", "false");
capability.setCapability("noReset", "true");
// capability.setCapability("unicodeKeyboard", "True");
// capability.setCapability("resetKeyboard", "True");

// 关键是加上这段
ChromeOptions options2 = new ChromeOptions();
options2.setExperimentalOption("androidProcess", "com.tencent.mm:tools");
capability.setCapability(ChromeOptions.CAPABILITY, options2);

// 启动微信浏览器
logger.info("启动微信浏览器");

driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capability);

// driver.manage().timeouts().implicitlyWait(500, TimeUnit.MILLISECONDS);
// Thread.sleep(5000);
//
// logger.info("点击微信搜索菜单");
// WebElement webElement = driver.findElement(By.xpath("//[@content-desc='搜索']"));
// webElement.click();
// logger.info("输入文件字符串");
// webElement = driver.findElement(By.xpath("//
[@text='搜索']"));
// webElement.click();
// webElement.clear();
// webElement.sendKeys("文件");
// Thread.sleep(5000);
// logger.info("点击文件传输助手");
// webElement = driver.findElement(By.xpath("//*[@text='文件传输助手']"));
// webElement.click();
// Thread.sleep(5000);
// logger.info("发送跳转网站的网页 URL");

// webElement = driver.findElement(By.xpath("//[@resource-id='com.tencent.mm:id/z4']"));// 不同微信版本,定位不一样
// webElement.sendKeys("http://192.168.1.103:8080/openurl/open.html");
// logger.info("点击发送按钮");
// webElement = driver.findElement(By.xpath("//
[@text='发送']"));
// webElement.click();
// Thread.sleep(5000);
// logger.info("点击网址");
// webElement = driver.findElement(By.xpath("//*[@text='" + "http://192.168.1.103:8080/openurl/open.html" + "']"));
// webElement.click();
// Thread.sleep(5000);
// logger.info("切换到微信 webView");

// Webdriver 转 AndroidDriver
AndroidDriver androidDriver = (AndroidDriver) driver;
androidDriver.context("WEBVIEW_com.tencent.mm:tools");
driver = androidDriver;

}

}

@jntdream ,请问你的问题解决了么,怎么解决的,能不能 share 下啊?
我遇到和你一样的问题了:(

有遇到切换 webview 的 context 时报 chrome not reachable 的吗,卡在这里好几天了!

需要 登录 後方可回應,如果你還沒有帳號按這裡 注册