十三、微信小程序自动化测试

小程序简介

  • 小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。
  • 小程序技术发展史
    • 小程序并非凭空冒出来的一个概念。当微信中的WebView逐渐成为移动Web的一个重要入口时,微信就有相关的JS API了。
  • 小程序的运行环境
image.png
  • 微信小程序运行在多种平台上: ios (iPhone/iPad)微信客户端、Android 微信客户端、PC微信客户端、Mac微信客户端和用于调试的微信开发者工具。
  • 各平台脚本执行环境以及用于渲染非原生组件的环境是各不相同的:
    • 在ioS 上,小程序逻辑层的javascript 代码运行在JavaScriptCore 中,视图层是由WKWebView来渲染的,环境有iOS 13、IOS 14等;
    • 在Android 上,小程序逻辑层的javascript 代码运行在V8中,视图层是由自研XWeb引擎基于MobileChrome内核来渲染的;
    • 在开发工具上,小程序逻辑层的javascript代码是运行在NW.js 中,视图层是由Chromium Webview来渲染的。
  • 平台差异
    • 尽管各运行环境是十分相似的,但是还是有些许区别:
      • JavaScript语法和APIl支持不一致︰语法上开发者可以通过开启ES6 转 ES5的功能来规避﹔此外,小程序基础库内置了必要的Polyfill,来弥补API的差异。
      • wXSS渲染表现不一致∶尽管可以通过开启样式补全来规避大部分的问题,还是建议开发者需要在iOS和Android 上分别检查小程序的真实表现。
      • 开发者工具仅供调试使用,最终的表现以客户端为准。
  • 美团小程序


    image.png
  • 与webview的方法在Chrome中使用inspect进行调试

微信调试开关

  • 微信每个版本都很"善变
    • 可手工开启调试开关
    • 默认关闭了调试开关而且无法开启
    • 默认开启调试开关
  • 手工开启办法
    • 文件传输助手发送:
      • debugtbs.qq.com
      • debugx5.qq.com
    • 打开微信小程序调试开关

微信小程序自动化测试

  • 微信小程序官方的自动化测试工具(不推荐)


    image.png

    image.png
  • 使用appium和selenium进行测试
    • 关键步骤
      • 设置chromedriver正确版本
      • 设置chrome option传递给chromedriver,由于微信与微信小程序是不同的进程,而appium的使用默认与进程相关,所以需要设置此参数
      • 使用adb proxy解决fix chromedriver的bug

为什么有些手机无法自动化微信小程序

  • 低版本的chromedriver在高版本的手机上有bug
  • chromedriver与微信定制的chrome内核实现上有问题
  • 解决方案:fix it
    • chromedriver没有使用adb命令,而是使用了adb协议
    • 参考提到的adb proxy源代码
  • adb proxy
    • mitmdump -p 5038 --rawtcp --mode reverse:http://localhost:5037/ -s adb_proxy.py

adb proxy 介绍

  • shell mock技术

    • 用于欺骗adb和appium,选择合适的chromedriver版本。个人使用可以先简单使用chromedriverExecutable代替
  • 协议mock adb proxy实现

    • 运行命令
      mitmdump -p 5038 --rawtcp --mode reverse:http://localhost:5037 -s tcp.py

    • 辅助小程序测试的adb_proxy.py

"""
mitmdump -p 5038 --rawtcp --mode reverse:http://localhost:5037/ -s adb.py
"""
from mitmproxy.utils import strutils
from mitmproxy import ctx
from mitmproxy import tcp

def tcp_message(flow: tcp.TCPFlow):
    message = flow.messages[-1]
    old_content = message.content
    #message.content = old_content.replace(b"foo", b"bar")
    message.content = old_content.replace(b"@webview_devtools_remote_", b"@.*.*.*._devtools_remote_")

    ctx.log.info(
        "[tcp_message{}] from {} to {}:\n{}".format(
            " (modified)" if message.content != old_content else "",
            "client" if message.from_client else "server",
            "server" if message.from_client else "client",
            strutils.bytes_to_escaped_str(message.content))
    )
  • 运行结果

mitmdump -p 5038 --rawtcp --mode reverse:http://localhost:5037/ -s /tmp/adb.py

Proxy server listening at http://*:5038
127.0.0.1:58593: clientconnect
127.0.0.1:58593 -> tcp -> localhost:5037 [tcp_message] from client to server:
000chost:version
127.0.0.1:58593 <- tcp <- localhost:5037
[tcp_message] from server to client:
OKAY00040029
127.0.0.1:58593: clientdisconnect
127.0.0.1:58596: clientconnect
127.0.0.1:58596 -> tcp -> localhost:5037
[tcp_message] from client to server:
000chost:devices
127.0.0.1:58596 <- tcp <- localhost:5037
[tcp_message] from server to client:
OKAY0000
127.0.0.1:58596: clientdisconnect

进行微信小程序自动化测试

  • 基本capability设置
DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
desiredCapabilities.setCapability( capabilityName: "platformName",value:"android");
desiredCapabilities.setCapability( capabilityName:"deviceName",value:"InsaneLoafer" );
desiredCapabilities.setCapability( capabilityName: "appPackage",value: "com.tencent.mm")
desiredCapabilities.setCapability( capabilityName:"appActivity" ,value:"com .tencent.m.ui.LauncherUI");
desiredCapabilities.setCapability( capabilityName: "unicodeKeyboard",value:"true");
desiredCapabilities.setCapability( capabilityName: "resetKeyboard",value: "true");
##高危操作,如果设置错误,聊天记录会被清空,建议使用小号测试
desiredCapabilities.setCapability( capabilityName: "noReset",value: "true");
  • chromedriver版本设置
//第一步:设置正确的chromedriver
//简单粗暴的解决方案
desiredCapabilities.setCapability(capabilityName: "chromedriveExecutable",
        value: " /chromedniver/chromednivers/ chromedriver_78.0.3904.11");
desiredCapabilities.setCapability( "chcomedrivecExecutable",
    "/chromedrcivec/chcomedrivers/chromedciver._2.23");
//完善的版本选择方案,不过会优先找android webview默认实现
desiredCapabilities.setCapability( "chromedcivecExecutableDir",
    "/ Users/seveniruby/projects/chcomedrivec/chromedcivecs");
desiredCapabilities.setCapability( "chromedrciverChromeMappingFile",
    "/Users/seveniruby/projects/Java3/src/main/resources/mapping.json" );
//打印更多chromedriver的log方便定位问题
desiredCapabilities.setCapability( capabilityName: "showChromedriverLog", value: true);
  • chromedriver参数配置
//第二步:设置chromeoption传递给chromedriver
//因为小程序的进程名跟包名不一样,需要加上这个参数
chromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setExperimentalOption( name: "androidPrgcess",value: "com.tencent.mm:appbrand0");
desiredCapabilities.setCapability( "goog:chromeOptions", chromeOptions);
//必须得加上,因为默认生成browserName=chrome的设置,需要去掉
desiredCapabilities.setCapability( capabilityName: "browserName",value:"");
  • 使用adb proxy
/第三步:设置adb proxy
//通过自己的adb代理修复chromedriver的bug并解决@xweb_devtools_remote的问题
desiredCapabilities.setCapability( capabilityName: "adbPort",value: "5038");

项目实战

  • 需要切换到可视化窗口


    image.png
  • 代码
#!/usr/bin/python3
# -*- coding: utf-8 -*-

from appium import webdriver
from selenium.webdriver import ActionChains, ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait


class TestwXMicro:
    # 为了演示方便,未使用page object模式
    def setup(self):
        caps = {}
        caps["platformName"] = "android"
        caps["deviceName"] = "InsaneLoafer"
        caps["appPackage"] = "com.tencent.mm"
        caps["appActivity"] = "com.tencent.mm.ui.LauncherUI"
        caps["noReset"] = True
        caps["unicodeKeyboard"] = True
        caps["resetKeyboard"] = True
        caps["chromedriverExecutable"] = '/projects/chromedriver/chromedrivers/chromedriver_78.0.3904.11'
        # options = ChromeOptions()
        # options.add_experimental_option('androidProcess', 'com.tencent.mm:appbrand0')
        caps["chromeOptions"] = {
            "androidProcess": "com.tencent.mm: appbrand0"
        }
        caps['adbPort'] = 5038
        self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
        self.driver.implicitly_wait(30)

        self.driver.find_element(By.XPATH, "//*[@text='通讯录']")
        self.driver.implicitly_wait(10)

    def test_search(self):
        # 原生自动化测试
        size = self.driver.get_window_size()
        self.driver.swipe(size['width'] * 0.5, size['height'] * 0.4, size['width'] * 0.5, size['height'] * 0.1)
        self.driver.find_element(By.CLASS_NAME, 'android.widget.EditText').click()
        self.driver.find_element(By.XPATH, "//*[@text='取消']")
        self.driver.find_element(By.CLASS_NAME, "android.widget.EditText").send_keys("雪球")
        self.driver.find_element(By.CLASS_NAME, 'android.widget.Button')
        self.driver.find_element(By.CLASS_NAME, 'android.widget.Button').click()
        self.driver.find_element(By.XPATH, "//*[@text='自选']")

        print(self.driver.contexts)

        # 进入webview
        self.driver.switch_to.context('WEBVIEW_xweb')
        self.driver.implicitly_wait(10)
        self.find_top_window()

        # css定位
        self.driver.find_element(By.CSS_SELECTOR, "[src*= stock_add]").click()  # 等待新窗口
        WebDriverWait(self.driver, 30).until(lambda x: len(self.driver.window_handles) > 2)
        self.find_top_window()
        self.driver.find_element(By.CSS_SELECTOR, "._input").click()
        # 输入
        self.driver.switch_to.context("NATIVE_APP")
        ActionChains(self.driver).send_keys("alibaba").perform()
        # 点击
        self.driver.switch_to.context("WEBVIEW_xweb")
        self.driver.find_element(By.CSS_SELECTOR, ".stock__item")
        self.driver.find_element(By.CSS_SELECTOR, ".stock__item").click()


    def find_top_window(self):
        """
        切换到可视化窗口的函数
        :param driver:
        :return:
        """
        for window in self.driver.window_handles:
            print(window)

            if ":VISIBLE" in self.driver.title:
                print("find")
                print(self.driver.title)
                return True
            else:
                self.driver.switch_to.window(window)
        return False

下一节:Appium设备交互API。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容