使用 KITE 进行 WebRTC 自动化测试

Posted by Piasy on March 28, 2020
本文是 Piasy 原创,发表于 https://blog.piasy.com,请阅读原文支持原创 https://blog.piasy.com/2020/03/28/KITE/

这两周在着手 OWT Server 的集群部署,为此必须先进行性能测试,原本我只根据一两个房间时的服务器负载情况推算单台机器能抗住多少房间,但忘篱大神一针见血:

一个房间占 6%,不代表三个房间占 18%,这里有个线性能力的问题,几百个线程的,要是能线性了,那也就真的奇迹再现。

但笔记本打开浏览器 tab 或多台手机测试,很容易就触发设备接入网带宽瓶颈,2.5Mbps 推流,实际上两人房间开到第三个就已经歇菜了。

所以必须在服务器上进行测试,好在已经有了现成的轮子:KITE。

简介

KITE 是 CoSMo 开发的 WebRTC 浏览器互通性测试框架,有开源版本,也有商用版本。KITE 是 Karoshi Interoperability Testing Engine 的缩写,Karoshi 是个日本词,意思是「过劳死」,KITE 就是为了防止测试人员做互通性测试时过劳死而开发的 :)

KITE 整合了 SeleniumAllure。Selenium 是一个开源的浏览器自动化框架,支持集群化(grids),不同的节点可以运行不同的操作系统类型/版本、浏览器类型/版本,这样我们就可以在各种操作系统和浏览器的组合下自动化测试 Web 应用了。Allure 则是一个开源的测试报告工具,用来展示测试结果。

安装

KITE 的安装需要科学上网,主要是浏览器自动化驱动和 Selenium 会从 Google 网站下载。

有了科学上网环境后,按照官方 README 执行即可,我在 macOS 上执行的命令为(假设你的命令行有 Java 8,必须 Java 8):

brew install maven
git clone https://github.com/webrtc/KITE.git
cd KITE
./configureMac.sh

configureMac.sh 这个脚本会启动系统 Terminal 应用执行脚本,所以弹出一堆 Terminal 窗口时不要惊慌。需要输入 y/n 的地方,都输 y 然后回车即可。等所有文件都下载完毕后,会自动启动本机的 Selenium grids。

Selenium grids 启动后,我们还需要编译 KITE,KITE 项目里写了几个简短命令,比如 c, r, a 等,它们在 scripts/mac/path 里,这里面的 r 命令和 zsh 内置的 r 命令冲突了,所以我建议运行上述命令时,切换到 bash 里。configureMac.sh 脚本会自动帮我们修改 .bash_profile,所以无需手动修改环境变量了。

在 KITE 根目录,bash 环境下,执行 c 命令,即可编译 KITE 了,这个过程会下载很多 maven 依赖,可能有点慢。

运行已有测例

运行测例之前,我们先了解一下 KITE 测例的设计思想。

KITE 测例采用了 Page Object Mode (POM) 的设计模式,所有查找、操作网页元素的代码,都写在 Page 类里,这些代码不属于测例逻辑,页面变更(比如标签重新组织)后,只需修改 Page 代码,无需修改测例逻辑。

测例逻辑有两类:Step 和 Check。Step 是对页面操作的逻辑,比如打开页面,点击加入按钮,Step 里不会有 assert 逻辑,所以它要么 Pass,要么 Break,而不会 Fail。Check 则是在执行完一定的操作后,对当前状态做 assert,比如三人房间里应该有三个 video 标签,每个 video 标签都在实际播放视频等,Check 可以 Fail。

了解完 POM 后,我们看一下已有测例 KITE-AppRTC-Test 目录,它分为三部分:

  • configs:测试配置文件;
  • js:JS 编写的测例;
  • src:Java 编写的测例;

KITE 支持使用 JS 或 Java 编写测例,KITE-AppRTC-Test 里的这两种测例逻辑是一样的,由于目前 JS 测例无法运行,所以我们只看 Java 测例。

继续在刚才编译 KITE 的命令行里:

cd KITE-AppRTC-Test
c
r configs/iceconnection.apprtc.config.json

c 命令是编译测例代码,r 则是用指定的配置文件执行测例。r 命令执行后,会打开两个 Chrome 浏览器实例,并加入同一个 https://appr.tc 的房间,然后检查 PC 的 iceConnectionStateconnectedcompleted

在科学上网环境下,如无意外,测例能通过。测试过程中,新启动的 Chrome 页面如下图:

这个测例除了检查 iceConnectionState,还会打开 chrome://webrtc-internals,并且存一份 webrtc_internals_dump.txt~/Downloads 里。

测例执行完毕后,r 命令会退出,之后我们可以运行 a 命令,它会启动一个 Terminal 执行 Allure 命令,稍等几秒钟,浏览器会自动打开一个测试报告网页,报告长这样:

我刚才就遇见了意外,网页没打开,所以最后失败了,点击 SUITES 里的任一个,可以查看详情:

新建测例

我们可以使用 KITE 提供的 kite_init 命令新建测例,在 KITE 根目录执行即可,例如:

kite_init OWT

上述命令会生成 KITE-OWT-Test 目录,其中包含了基本测例模板,这个模板会打开 config 中指定的 URL,并检查两个随机数的大小关系。目前 kite_init 脚本生成的 Java 测例代码有点小问题,生成的 MainPage.javaopen 函数编译不过:

  public void open(String url) {
    loadPage(webDriver, url, 20);
  }

将其改为如下即可:

  public void open(String url) {
    webDriver.get(url);
  }

具体测例代码的编写,这里就不展开了,参考一下现有的 KITE-AppRTC-Test,问题都不大。

不过由于 OWT 的 JS 代码无法获取 PC,所以要写测例还得稍微修改一下。修改 src/sdk/conference/client.js,为 ConferenceClient 增加一个获取 PC 的属性:

  Object.defineProperty(this, 'channels', {
    configurable: false,
    get: () => {
      return Array.from(channels.values());
    },
  });

然后我们就可以通过 conference.channels[i]._pc 来获取 PC 了。

测例完整代码,可以从 GitHub 获取,在 owt-test 分支。

原理分析

别怕,这里只是稍微了解一下测例运行的过程,以便我们编写测例,或者排查问题,如果实在不感兴趣,可以跳到后面的小节

运行测试的 r 命令,位于 scripts/mac/path/r 中,它负责启动 KITE-EngineEngine.javamain 函数,并把我们传的 config 路径传进去。启动时的 classpath 包括 KITE-Engine 和测例目录的 Java 代码编译出来的 jar。

config 里需要重点关注的有三个字段:

  • grids:指定 Selenium grids 的地址;
  • tests:测例列表,每个测例需要关注的字段有:
    • tupleSize:测例涉及的浏览器实例数量,测试初始化时会同时启动这么多个浏览器进程;
    • testImpl:测例入口代码文件,如果是 JS 测例,则指定入口 JS 文件名,如果是 Java 测例,则指定入口完整包名类名;KITE Engine 中正是根据 testImpl 是否以 .js 结尾来判断是否为 JS 测例的;
    • payload:会被传递到测例代码的数据;
  • clients:进行测试的浏览器类型列表,kite_init 生成的模板里会包含 chromefirefox 两种浏览器;

对于 JS 测例,KITE-Engine 最终会在测例的 js 目录中,通过 node 命令执行 testImpl 指定的测例入口代码文件,比如:node OWT.js 1 0 32fafb49-b6d5-46ca-b03c-e8ab2246f6eb ./temp/OWT.js_2020-03-28-102511_e152d。由于目前 JS 测例无法运行,这里我们就不展开了。

Java 测例则是在 TestManager.javabuildTest 函数中,从测例代码的 jar 里实例化测试类,之后调用其中的函数执行我们编写的测例逻辑。实例化测试类使用的完整包名类名正是由 testImpl 指定。

Java 测例运行时的调用栈:

  • Engine.java main
  • Engine.java runInterop
  • TestRunThread.java call
  • MatrixRunner.java run
  • TestManager.java call
  • KiteBaseTest.java execute
  • KiteBaseTest.java testInParallel
  • TestRunner.java call
  • TestStep.java processTestStep
  • TestStep.java execute
  • TestStep.java step

TestManager.javabuildTest 函数,会在 TestManager.java call 里调用。

KiteBaseTest.java execute 里会调用 KiteBaseTest.java init,最终会先后调用到我们的 Test 子类的 payloadHandlingpopulateTestSteps 函数,在前者里我们可以解析 config 的 payload 内容,在后者里则需要我们添加我们实现的 Step 子类实例。

比如:

public class KiteOWTTest extends KiteBaseTest {
  @Override
  protected void payloadHandling() {
    super.payloadHandling();
    //process payload here
  }

  @Override
  public void populateTestSteps(TestRunner runner) {
    runner.addStep(new OpenUrlStep(runner, url));
    runner.addStep(new MyFirstCheck(runner));
  }
}

Step 实例添加后,TestStep.java step 函数最终会被执行,这是个虚函数,由我们实现的子类实现,比如:

public class OpenUrlStep extends TestStep {
  private final String url;
  private final MainPage mainPage;
  
  public OpenUrlStep(Runner runner, String url) {
    super(runner);
    this.url = url;
    this.mainPage = new MainPage(runner);
  }
  
  @Override
  public String stepDescription() {
    return "Open " + url;
  }
  
  @Override
  protected void step() throws KiteTestException {
    mainPage.open(url);
  }
}

mainPage.open 的实现为:

public void open(String url) {
  webDriver.get(url);
}

webDriver 是 Selenium Java SDK 的类,利用它,我们就可以让 Selenium 集群里的节点执行我们指定的动作了,比如新建 tab 打开网页,点击 DOM 节点、发送键盘事件等。

Check 也是 TestStep 的子类,所以我们也是在 populateTestSteps 中添加,并在其 step 函数中实现检查逻辑。

测例运行的过程我们已经基本了解了,但测例运行时推流的音视频是哪里来的呢?

其实 Chrome 和 Firefox 都有一些测试选项,让我们可以很方便地进行自动化测试,比如:

  • --use-fake-device-for-media-stream:音视频采集不使用麦克风和相机,而是使用假数据(也就是我们听到的「嘟嘟」和看到的「转圈」);
  • --use-fake-ui-for-media-stream:不弹音视频权限请求对话框;
  • --use-file-for-fake-audio-capture=<filename>:音频采集不使用麦克风,而是使用指定文件的内容;
  • --use-file-for-fake-video-capture=<filename>:视频采集不使用相机,而是使用指定文件的内容;

WebDriverFactory.javasetCommonChromeOptions 函数中,就设置了 --use-fake-device-for-media-stream 等参数。

公有云集群

本地测试已经跑起来了,下一步就是在公有云上跑了,公有云的带宽都是百兆千兆的,跑几十上百个房间 so easy。

实际上我们只需要在公有云上部署一个 Selenium grids 即可,Chrome 和 Firefox 浏览器都支持 headless 模式,没有图形界面也可以测 WebRTC。而 Selenium 有现成的 docker 镜像,所以这一步非常简单。

编写 docker-compose.yaml

version: "3"
services:
  selenium-hub:
    image: selenium/hub:3.141.59-20200326
    container_name: selenium-hub
    ports:
      - "4444:4444"

  chrome1:
    image: selenium/node-chrome:3.141.59-20200326
    volumes:
      - /dev/shm:/dev/shm
      - /home/ubuntu/selenium:/fake_data
    depends_on:
      - selenium-hub
    environment:
      - HUB_HOST=selenium-hub
      - HUB_PORT=4444
      - NODE_MAX_INSTANCES=5

  chrome2:
    image: selenium/node-chrome:3.141.59-20200326
    volumes:
      - /dev/shm:/dev/shm
      - /home/ubuntu/selenium:/fake_data
    depends_on:
      - selenium-hub
    environment:
      - HUB_HOST=selenium-hub
      - HUB_PORT=4444
      - NODE_MAX_INSTANCES=5

在服务器上安装 docker 和 docker-compose 之后,在 docker-compose.yaml 所在目录执行 docker-compose up,就成功启动了包含两个 Chrome 节点的 grids,每个节点支持同时跑 5 个实例。如果需要测试不同的浏览器或版本,换镜像和 tag 即可。

开放服务器 TCP 4444 端口后,修改 configs/owt.config.jsongrids 字段即可:

  "grids": [
    {
      "type": "local",
      "url": "http://<selenium grids IP>:4444/wd/hub"
    }
  ]

注意替换实际服务器公网 IP。

另外,Chrome 默认的假视频数据编码无法达到 2.5Mbps,所以我从生活大爆炸里截取了一段两分钟的视频,利用 ffmpeg 将其转换为 y4m 格式:

ffmpeg -i TheBigBangTheoryS09E01-high.mp4 TheBigBangTheoryS09E01-high.y4m

注意,y4m 是未编码格式,转出来的视频很大,我这个 1080p 的两分钟就有将近 9GB。

准备好视频后,修改 configs/owt.config.jsonclients 字段即可:

  "clients": [
    {
      "browserName": "chrome",
      "platform": "LINUX",
      "video": {
        "filename": "TheBigBangTheoryS09E01-high",
        "directory": "/fake_data/",
        "type": "Video"
      }
    }
  ]

/fake_data/ 是 docker 里的路径,主机路径为 /home/ubuntu/selenium,把视频文件放在其中即可。这个视频最终会通过 --use-file-for-fake-video-capture 传给 Chrome,于是相机采集到的内容就是这个视频文件的内容了。

Native App

KITE 号称还支持 Native App,不过目前没有看到有具体的文档,后续得到 CoSMo 的回复后,若能公开使用,再分享给大家。


欢迎大家加入 Hack WebRTC 星球,和我一起钻研 WebRTC。

piasy-knowladge-planet