Skip to content

微信小程序自动化测试


微信小程序自动化测试方法

方式 技术栈 优点 缺点
原生自动化 uiautomator
appium
atx
简单
不依赖 webview 调试开关开启
不易维护
web 自动化 selenium
chromedriver
minitest
易维护 不适合混合开发
依赖 webview 调试开关开启
小程序不支持浏览器直接访问,此方法基本不可用
混合自动化 appium 易维护,通用 技术复杂
依赖 webview 调试开关开启

方法选型常见问题

  • 使用原生自动化测试方法
  • 个别微信版本可能会遇到 uiautomator2 的 bug 导致无法自动化
  • 定位符基本不可维护,需要研发配合
  • 使用 webview 自动化测试方法
  • 个别微信版本可能会遇到无法开启远程调试开关


基于 webview 自动化技术测试小程序

  • 上下文切换
  • 开启微信小程序的调试模式
  • 选对 chromedriver 版本
  • 修复 appium webview 与 xweb 的转换 bug
  • 窗口切换
  • 输入问题

小程序调试开关

  • 如果是 x5 内核,请开启调试开关
  • 在聊天窗口输入网址并打开即可进入 http://debugtbs.qq.com
  • 如果是非 x5 内核,默认是开启的
  • x5 内核切换开关 http://debugmm.qq.com/?forcex5=true
  • 出现对应的 domain sockets 代表成功


chromedriver 版本选择

  • 禁用 chromedriverExecutableDir
  • 微信使用了多种不同版本的 webview 内核,会出现识别错误
  • 开启 chromedriverExecutable
  • 控制 chromedriver 版本

appium webview 上下文识别 bug 修复

  • appium 在切换上下文时,会把 xweb 标记错误替换为 webview
  • 指令直接通过 adb 的 5037 端口发送
  • 通过学社独家首创提供的 adb_xweb_mock 工具修复

ceshiren.com: ~ seveniruby$ adb shell cat /proc/net/unix | grep devtools
0000000000000000: 00000002 00000000 00010000 0001 01 11078370 @xweb_devtools_remote_31384
0000000000000000: 00000002 00000000 00010000 0001 01 11081895 @webview_devtools_remote_31145
0000000000000000: 00000002 00000000 00010000 0001 01 11073145 @xweb_devtools_remote_30462
0000000000000000: 00000002 00000000 00010000 0001 01 11073151 @webview_devtools_remote_30462
0000000000000000: 00000002 00000000 00010000 0001 01 11078388 @webview_devtools_remote_31384

窗口切换

  • 小程序的每个界面都是新开窗口
  • 需要按需切换窗口,可通过标题中的:VISIBLE或者 url 进行切换


输入问题

  • 小程序的输入控件有特殊设计,无法直接在 webview 下进行 send_keys
  • 可以通过切换到原生去 send_keys 解决

触发直接输入事件 mobile: type

Types the given Unicode string. It is expected that the focus is already put to the destination input field before this method is called. The main difference between this method and the sendKeys one is that it emulates `true` typing like it was done from an on-screen keyboard. It also properly supports Unicode input characters. | Name | Type | Required | Description | Example | | ---- | ------ | -------- | ---------------- | ------- | | text | string | yes | The text to type | testing |

总结

鉴于这么多的坑,最好是封装自己的框架,以规避底层框架的问题


微信小程序自动化测试代码 python 版本

class TestWechat:
    def setup_class(self):
        capabilities = {
            'platformName': 'android',
            # 为了不清理微信数据
            'noReset': True,
            'appPackage': 'com.tencent.mm',
            'appActivity': 'com.tencent.mm.ui.LauncherUI',
            'unicodeKeyboard': True,
            'resetKeyboard': True,
            'showChromedriverLog': True,
            # appium bug 切换context时会把xweb当成webview,需要借助adb xweb mock技术修复
            'adbPort': 5038,
            'chromedriverExecutable': '/Users/seveniruby/projects/chromedriver/chromedrivers/chromedriver_86.0.4240',
            # appium bug 会自动覆盖自己配置的mapping file,太智能了反而导致了bug
            # 'chromedriverChromeMappingFile': '/Users/seveniruby/PycharmProjects/AppiumDemo/mapping.json',
            # 'chromedriverExecutableDir': '/Users/seveniruby/projects/chromedriver/chromedrivers'
        }
        self.driver = webdriver.Remote('http://localhost:4723/wd/hub', capabilities)
        self.driver.implicitly_wait(10)

    def test_wechat(self):
        self.driver.find_element(By.CSS_SELECTOR, '*[description="搜索"]').click()
        self.driver.find_element(By.CSS_SELECTOR, 'android.widget.EditText').send_keys("美团外卖")
        self.driver.find_element(By.XPATH, '//*[contains(@text, "外卖美食")]').click()

        print(self.driver.contexts)
        wait = WebDriverWait(self.driver, 5)
        # 个别app webview组件加载慢的时候不一定会及时出现webview上下文,需要加显式等待
        wait.until(lambda driver: filter(lambda c: "appbrand" in c, self.driver.contexts))
        print(self.driver.contexts)

        # 进入第一个打开的小程序
        self.driver.switch_to.context("WEBVIEW_com.tencent.mm:appbrand0")
        self.driver.implicitly_wait(10)
        self.switch_to_visible_window("/index/index.html")

        search_button = self.driver.find_element(By.CSS_SELECTOR, '.search-index--ellipsis')
        wait.until(expected_conditions.element_to_be_clickable(search_button))
        search_button.click()

        self.switch_to_visible_window("/search/search.html")
        self.driver.find_element(By.CSS_SELECTOR, '#search_input').click()

        print("输入")
        # 只能用这个办法输入
        self.driver.switch_to.context("NATIVE_APP")
        self.driver.execute_script("mobile: type", {"text": "啤酒炸鸡"})
        self.driver.switch_to.context("WEBVIEW_com.tencent.mm:appbrand0")

        print("点击搜索按钮")
        self.driver.find_element(By.CSS_SELECTOR, '#search_button').click()

        print("后退")
        self.driver.switch_to.context("NATIVE_APP")
        self.driver.back()
        self.driver.switch_to.context("WEBVIEW_com.tencent.mm:appbrand0")

        print("断言")
        self.switch_to_visible_window("/search/search.html")
        assert "啤酒炸鸡" in self.driver.find_element(By.CSS_SELECTOR, '[data-history=true]').text

    def switch_to_visible_window(self, pattern=":VISIBLE"):
        signal = False
        while not signal:
            for window in self.driver.window_handles:
                print(window)
                self.driver.switch_to.window(window)
                print(self.driver.current_url)
                print(self.driver.title)
                # 进入可视化的页面
                if pattern in self.driver.title:
                    print(pattern)
                    signal = True
                    break


微信小程序自动化测试代码 java 版本

public class WechatTest {

    private static AndroidDriver driver;

    @BeforeAll
    public static void beforeAll() throws MalformedURLException {
        DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
        desiredCapabilities.setCapability("platformName", "android");
        desiredCapabilities.setCapability("noReset", true);
        desiredCapabilities.setCapability("appPackage", "com.tencent.mm");
        desiredCapabilities.setCapability("appActivity", "com.tencent.mm.ui.LauncherUI");

        desiredCapabilities.setCapability("adbPort", 5038);
        desiredCapabilities.setCapability("unicodeKeyboard", true);
        desiredCapabilities.setCapability("resetKeyboard", true);

        desiredCapabilities.setCapability("showChromedriverLog", true);
        //设置保存有所有chromedriver的一个目录,让appium自动发现对应的版本
        desiredCapabilities.setCapability(
                "chromedriverExecutable",
                "/Users/seveniruby/projects/chromedriver/chromedrivers/chromedriver_86.0.4240");
        URL url = new URL("http://127.0.0.1:4723/wd/hub");
        driver = new AndroidDriver(url, desiredCapabilities);
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
    }

    @Test
    public void wechat() {
        driver.findElement(By.cssSelector("*[description='搜索']")).click();
        driver.findElement(By.cssSelector("android.widget.EditText")).sendKeys("美团外卖");
        driver.findElement(By.xpath("//*[contains(@text, '外卖美食')]")).click();

        System.out.println(driver.getContextHandles());
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
        wait.until(webDriver -> driver.getContextHandles().stream().anyMatch(context -> context.contains("appbrand")));
        System.out.println(driver.getContextHandles());

        driver.context("WEBVIEW_com.tencent.mm:appbrand0");
        switch_to_visible_window("/index/index.html");

        WebElement search_button = driver.findElement(By.cssSelector(".search-index--ellipsis"));
        wait.until(ExpectedConditions.elementToBeClickable(search_button));
        search_button.click();

        switch_to_visible_window("/search/search.html");
        driver.findElement(By.cssSelector("#search_input")).click();

        driver.context("NATIVE_APP");
        HashMap<String, Object> map = new HashMap();
        map.put("text", "啤酒炸鸡");

        driver.executeScript("mobile: type", map);
        driver.context("WEBVIEW_com.tencent.mm:appbrand0");

        driver.findElement(By.cssSelector("#search_button")).click();

        driver.context("NATIVE_APP");
        driver.navigate().back();
        driver.context("WEBVIEW_com.tencent.mm:appbrand0");

        switch_to_visible_window("/search/search.html");
        assertThat(
                driver.findElement(By.cssSelector("[data-history=true]")).getText(),
                containsString("啤酒炸鸡"));


    }

    public void switch_to_visible_window(String pattern) {
        boolean signal = false;
        while (!signal) {
            Set<String> windows = driver.getWindowHandles();
            for (String window : windows) {
                driver.switchTo().window(window);
                String url = driver.getCurrentUrl();
                String title = driver.getTitle();
                System.out.println(url);
                System.out.println(title);
                if (title.contains(pattern)) {
                    signal = true;
                    break;
                }
            }
        }

    }
}