STF平台搭建及二次开发

1. STF简要功能介绍

1.1 什么是STF?

STF(Smartphone Test Farm)是一个移动设备管理平台,可以对移动设备进行远程管理、调试、远程桌面监控等操作。

这个系统类似于目前很流行的云测服务,但是目前只适合内部系统使用,从设计之初就没有过多考虑多用户及数据安全性。

虽然网页上提供的设备很像模拟器,但是实际上都是真机,因此安装到设备上的应用流畅性以及运行环境的真实性可以得到保证。

STF代码开源,原始团队openstf已解散,原项目openstf/stf不再维护。原始团队推荐了一个fork项目DeviceFarmer/stf,目前在缓慢迭代中,本文基于DeviceFarmer/stf的3.6.1版本撰写。

1.2 STF支持的功能有什么?

  • 通过浏览器远程控制任何设备
  • 实时屏幕视图
  • 刷新速度可以达到30-40 FPS,具体取决于规格和Android版本。有关更多信息,请参见minicap
  • 支持屏幕旋转
  • 支持使用自己的键盘输入文字
  • 支持中继键
  • 复制和粘贴支持(尽管在较旧的设备上可能有点挑剔,但您可能需要长按并手动选择粘贴)
  • 不幸的是,可能不适用于非拉丁语言
  • 通过minitouch在触摸屏上提供多点触摸支持,Alt在拖动时按住可以在常规屏幕上支持两根手指捏/旋转/缩放手势
  • 拖放安装和启动.apk文件
  • 通过minirev反向端口转发
  • 即使设备不在同一网络上,也可以直接从设备访问本地服务器
  • 在任何浏览器中轻松打开网站
  • 实时检测已安装的浏览器,并显示为可选选项
  • 如果用户选择默认浏览器,则会自动检测到
  • 执行shell命令并查看实时输出
  • 显示和过滤设备日志
  • 可以使用adb connect连接远程设备
  • adb可以在本地运行任何命令,包括shell访问
  • Android Studio和其他IDE支持,在浏览器上观看设备屏幕的同时调试您的应用程序
  • 支持Chrome远程调试工具
  • 提供文件资源管理器以访问设备文件系统
  • VNC实验支持(正在进行中)
  • 监控您的设备清单
  • 查看哪些设备已连接,离线/不可用(表明USB连接弱),未授权或已拔出
  • 查看谁在使用设备
  • 通过电话号码,IMEI,ICCID,Android版本,运营商,产品名称,组名称和/或许多其他属性来搜索设备,并具有简单但功能强大的查询
  • 在需要物理定位的设备上显示带有标识信息的亮红色屏幕
  • 跟踪电池电量和健康状况
  • 基本的Play商店帐户管理
  • 显示,删除和添加新帐户(添加可能无法在所有设备上进行)
  • 显示硬件规格
  • 简单的 REST API

2. STF基本搭建流程

2.1 STF的前置依赖

  • 需要Node.js 8.x (某些依赖项不支持较新的版本)
  • adb正确设置(安卓调试桥)
  • RethinkDB > = 2.2(数据库)
  • GraphicsMagick(用于调整屏幕截图的大小)
  • 安装了ZeroMQ库
  • 已安装protobuf库
  • 安装了yasm(用于编译嵌入式libjpeg-turbo)
  • pkg-config

请注意,即使直接从NPM安装了STF,也需要这些依赖项,因为它们不能包含在软件包中。

在Mac OS上,可以使用brew安装大多数依赖项:

brew install rethinkdb graphicsmagick zeromq protobuf yasm pkg-config

但上述代码很可能执行失败,届时根据brew的提示进行操作,可能涉及从源码安装、缺失依赖安装、无权限处理等,最终耗时可能比较长。

官方推荐在Linux发行版上安装使用STF,开发团队的开发环境为CoreOS,因此使用Linux发行版可能搭建过程较为容易,生产体验可能较其他系统更好。

2.2 npm简易安装STF

安装好前置依赖之后就比较简单了,这里通过npmjs安装STF,仅需一行代码(如果顺利的话):

npm install -g stf

静静等待安装完成,如果执行失败,届时根据提示进行操作,可能涉及从源码安装、缺失依赖安装、无权限处理等。

2.3 运行及登录方式

2.3.1 STF基本运行

安装完成后,先开一个窗口运行rethinkdb,再开一个窗口运行stf,之后就可以通过 http://localhost:7100/ 访问stf平台了,具体代码如下:

rethinkdb

stf local

【注意】要分两个窗口分别运行,先运行 rethinkdb ,再运行 stf local
如果 RethinkDB 启动花费了很长时间,可能是遇到了 rethinkdb/rethinkdb#4600rethinkdb/rethinkdb#6047 问题。通常发生在 macOS Sierra 上,需要先运行 scutil --get HostName 以检查 HostName 变量是否未设置,RethinkDB 需要它为生成服务器名称。如果它是空的,执行 sudo scutil --set HostName $(hostname) 可以解决该问题。

上述代码属于基本使用,建议将上述代码替换为如下代码:

rethinkdb --bind all --cache-size 8192 --http-port 8090

stf local --public-ip <ip_here> --port <port_here> --allow-remote

执行完成后,你可以通过 http://<ip_here>:<port_here>/ 访问stf平台。如果将<port_here>设置为80,则无需输入端口号即  http://<ip_here> ,另外还可以将<ip_here>设置为域名。

2.3.2 mock登录方式

STF默认以mock登录方式运行,无需特别指定,2.3.1中的代码即为mock登录方式运行的。

除了本地用户之外,STF 还提供管理员级别,增加了对某些功能的权限(例如预订和分区系统、释放占用的设备、用户和设备管理等)。相应的内置管理员用户信息如下:

名称: administrator
电子邮件: administrator@fakedomain.com

STF还有另一个内置对象,是用户和设备第一次注册到STF数据库时所属的根标准组,其默认名称为 Common。

可以通过设置以下环境变量来覆盖这些内置对象的默认值,然后通过stf local(之前未运行过STF)或stf migrate(之前已运行过STF)命令初始化 STF 数据库:

  • 根标准组名称: STF_ROOT_GROUP_NAME
  • 管理员用户名: STF_ADMIN_NAME
  • 管理员用户邮箱: STF_ADMIN_EMAIL

2.3.3 ldap登录方式

另外STF还可以支持LDAP登录,可以接入公司的LDAP服务,部分信息需要咨询公司的LDAP系统负责人,以LDAP登录方式启动的命令如下:

sudo stf local --public-ip <ip_here> --port <port_here> --auth-type ldap --auth-options '["--app-url","<app_url>","--ldap-url","<ldap_url>","--ldap-search-dn","<ldap_dn>","--ldap-search-class","<ldap_class>","--ldap-search-field","<ldap_id>","--ldap-username-field","<ldap_name>"]' --allow-remote

除了之前需要的--public-ip和--port,还有比较多的新增参数,我们一个个分析:

  • --auth-type ldap:指定ldap登录方式
  • --auth-options:ldap登录需要的参数
  • "--app-url","<app_url>":STF的访问url,应填写http://<ip_here>:<port_here>/,用于因重置密码等逻辑跳转ldap系统后返回STF
  • "--ldap-url","<ldap_url>":ldap系统url,通常为ldaps://ldap.<xxx>.com/,请询问ldap系统负责人
  • "--ldap-search-dn","<ldap_dn>":ldap系统dn信息,例如dc=xxx,dc=com,请询问ldap系统负责人
  • "--ldap-search-class","<ldap_class>":ldap用户存储表名,请询问ldap系统负责人
  • "--ldap-search-field","<ldap_id>":ldap用户标识字段名,匹配输入的username,类似账号,请询问ldap系统负责人
  • "--ldap-username-field","<ldap_name>":ldap用户名称显示字段名,匹配显示的用户名称displayName,类似昵称,请询问ldap系统负责人

3. STF的分布式部署

3.1 ADB远程连接其他计算机上的Android设备

主机上USB接口有限,接普通的USB HUB会有供电不足的问题,如果用自供电的USB HUB也会有数据传输速率低延迟高的问题。

考虑过启动adb的WiFi连接模式,但传输的数据不够稳定,容易出现断开连接的情况。

最后发现adb可以进行远程连接,在STF服务器上远程连接其他设备上的adb服务,可以稳定扩张有限的USB接口,充分利用其他设备多余的USB接口。

首先需要在待连接的目标设备上运行adb服务(没有adb的话要装一个),端口号是5037,最好不要改端口号:

adb nodaemon server -a -P 5037

之后在STF服务器上运行命令(记得要填入远程设备的IP地址):

stf provider --name <服务器名称> --min-port 7400 --max-port 7700 --connect-sub tcp://127.0.0.1:7114 --connect-push tcp://127.0.0.1:7116 --group-timeout 900 --public-ip <ip_here> --storage-url http://<ip_here>:<port_here>/ --adb-host <远程设备ip地址> --adb-port 5037 --vnc-initial-size 600x800 --mute-master never --allow-remote

服务器名称可随意取,http://<ip_here>:<port_here>/即STF服务器访问url,远程设备ip地址是充当USB HUB的计算机ip地址。

运行成功后,STF服务器就可以连接到远程设备上的安卓手机了,但是无法读取到正在连接的状态,插上安卓手机后要等手机上的STF APP完全启动才能正常操作,只影响刚插上的手机。

3.2 多台STF服务器远程连接

STF支持分布式部署,具体是否需要相同的版本和相同的登录方式还未实践确定。

首先在服务器上启动STF服务,按本文【2. STF基本搭建流程】执行即可。

之后在服务器上启动STF服务,但不要指定到主服务的域名上。

最后在服务器上执行命令,其中http://<ip_here>:<port_here>/服务器访问url:

stf provider --name <设备名> --min-port 7400 --max-port 7700 --connect-sub tcp://<ip_here>:7114 --connect-push tcp://<ip_here>:7116 --group-timeout 20000 --public-ip <ip_here> --storage-url http://<ip_here>:<port_here>/ --vnc-initial-size 600x800 --allow-remote

4. STF二次开发入门

4.1 本地编译STF

要二次开发STF,也需要先了解安装即运行,参考本文【2. STF基本搭建流程】,本节内容替换【2.2 npm简易安装STF】,则是一个完整的本地编译运行过程。

首先按【2.1 STF的前置依赖】完成前置依赖的配置,随后需要下载STF的源码,参考项目DeviceFarmer/stf

进入源码文件夹执行命令:

npm install

如果npm执行失败,根据提示进行操作,可能涉及从源码安装、缺失依赖安装、无权限处理等。

npm执行完成后会自动执行bower相关命令,需要获取相关的bower依赖。

如果是bower执行失败,很可能是GitHub上存储的部分仓库访问失败,大概率是需要给终端配置代理环境,需要在终端中执行:

export http_proxy=<http_proxy>
export https_proxy=<https_proxy>

需要一个代理环境,<http_proxy>和<https_proxy>可以是一样的,配置好代理之后,需要重新执行npm命令。

如果还有某个bower的依赖安装失败了,那么很可能是这个依赖的GitHub仓库没了。

没错,bower是实时从GitHub上拉源码的,而且STF依赖的三方开源库还挺多的,所以万一有人删库跑路了,我们本地编译也会挂。

这时候看看报错是哪个库没了,然后看看DeviceFarmer最新的master分支上的bower.json文件和本地对比一下,看看那个库有没有更新一个新的GitHub仓库来源。

如果有,那么同步一下重新编译。如果没有,那就自己去找一个fork的仓库吧,GitHub或者Gitee都行,找到以后自己建个GitHub仓库更新一下本地的bower.json,再重新编译。

顺利安装所有依赖以后,需要先进行gulp构建,执行命令:

gulp clean && gulp webpack:build

glup构建完成后,执行npm命令(Mac上需要增加sudo):

sudo npm link

最后就可以执行stf的启动命令了,参考【2.3 运行及登录方式】。

4.2 前端二次开发简介

STF涉及的相关知识点有:

  • webpack
  • gulp
  • angularjs
  • bower
  • Promise
  • websocket
  • protobuf

前端入口js是res/app/app.js,各个构成模块如下:

angular.module('app', [
  'ngRoute',
  'ngTouch',
  require('gettext').name,
  require('angular-hotkeys').name,
  require('./layout').name,
  require('./device-list').name,
  require('./group-list').name,
  require('./control-panes').name,
  require('./menu').name,
  require('./settings').name,
  require('./docs').name,
  require('./user').name,
  require('./../common/lang').name,
  require('stf/standalone').name,
  require('./group-list').name
])

前端相关的代码都在res文件夹下,目录结构如下:

我们看到/res/app下面是各个模块的前端代码。每个模块基本上都是由一个index.js、对应的controller.js、对应的pug、对应的css组成,这些模块组合在一起,就组合成了我们的看到的前端页面。如果需要增加自己开发的前端页面,需要将你的模板加入到其中去。

每一个模块的入口是他们各自的index.js。

在对应的index.js里面里面,定义了某个路由对应的pug模板以及controller。对应的pug模板就是前端要展示的页面,controller通过和后端通信,给pug提供展示的数据。

这块引用了很多/res/app/components/stf/下面对应的service。service会用到Websocket与服务端进行通信。

所以综上,如果要增加一个页面,需要在app.js,增加require(‘./xx’).name,需要增加xx目录下的index.js,xx-controller.js,xx.pug,以及在/res/app/components/stf目录下增加xx-service.js。

修改前端代码后需要执行命令:

gulp clean && gulp webpack:build

另外STF内置汉化,可以在设置页面自行切换,也可在res/app/components/stf/language/language-service.js中替换默认语言:

    LanguageService.settingKey = 'selectedLanguage'
    LanguageService.supportedLanguages = supportedLanguages
    LanguageService.defaultLanguage = 'zh_CN'
    LanguageService.detectedLanguage =
      onlySupported(detectLanguage(), LanguageService.defaultLanguage)

目前STF对多语言的支持还不是特别完善,可以参考stf#translating,完善一下STF的多语言支持。

4.3 后端二次开发简介

后端代码都在stf/lib目录下,一般启动stf服务器用的是node bin/stf local,顺着bin/stf文件很轻易找到cli.js。这个js使用command.js定义了大量的node命令,这些命令又调用util/procutil.js创建大量进程。

具体进程可以看下openstf官方给出的这张图,虽然现在DeviceFarmer有做一些改动,但大致框架应该变化不大:

后端代码目录结构如下:

服务端处理信息流转有三个文件,cli/websocket/index.js、cli/triproxy/index.js、cli/processor/index.js 。

db是数据操作文件,STF使用的数据库是rethinkdb,rethinkdb是以json方式存储数据的。

主要的逻辑分为db/api.js、db/index.js、db/setup.js、db/tables.js。

db的操作只需要修改db/tables.js与db/api.js即可,其它两个文件可以不用修改,db/api.js主要用于数据库增删查操作。

units是核心代码,根据其命名可得知其作用。

修改后端代码后需要先停止STF服务,然后执行命令:

sudo npm link

最后再启动STF服务。

5. STF后续发展之路

5.1 功能扩展

5.1.1 iOS支持

STF本身是不支持iOS设备的,但是实际使用中也有iOS设备的需求,最好也要对iOS设备进行支持。

DeviceFarmer/stf将支持iOS设备作为了长期目标,看起来是无法在短时间内实现的。

目前有尝试对iOS设备添加支持能力,虽然成功了,但没有达到Android的使用效果,流畅度和清晰度都不足。

5.1.2 摄像头模拟

STF使用的都是真机,需要摄像头的话也是用了真机的摄像头,对于一些用户想要进行扫描二维码、人脸识别、图像识别等测试时就捉襟见肘了。

可以尝试将图片、视频通过STF传输到真机上,模拟摄像头输入。最好是可以接入计算机的摄像头(如果有的话),将计算机的摄像头反向代理给STF连接的真机,不过考虑到带宽问题应该不好实现。

5.1.3 集成H5调试

STF支持Chrome远程调试工具,可以调试设备上的H5页面,但是需要连接的话需要一个比较长的链路,不是特别方便。

如果可以内置H5调试工具,通过一个按钮或者一个面板开启,会方便很多。

5.1.4 跨屏复制粘贴

STF目前可以读取真机的剪贴板,将最近一条复制的内容显示出来。但是无法读取计算机的剪贴板,不能将内容粘贴到连接的真机上,也不能达到在真机上复制直接在计算机上粘贴的能力。

希望可以打通真机和计算机的剪贴板,在真机上复制可以直接在计算机上粘贴,反过来也一样,这样才是一个顺畅的跨屏复制粘贴能力。

5.2 优化改进

5.2.1 触屏流畅性

由于设备带宽有限,录屏 -> STF -> 计算机链路上需要花费一些时间,导致触屏会有一点点延迟,录屏的质量越高延迟越高,设备越多反应越慢,STF刷新速度基本上处于30-40 FPS甚至会更低一些。

Genymobile/scrcpy有相应的解决方案,直接通过反射调用安卓的SurfaceControl私有API,把屏幕传给MediaCodec,由系统去做录屏,最终编码成H264,通过websocket发送给前端的video做展示。可能刷新速度可以达到60 FPS,并且流量能通过编码器的码率控制,由于视频的压缩方式优势视频流能极大的减少了带宽,也不用考虑带宽过大的问题。

5.2.2 中文输入

目前大多数设备上只能响应英文输入,即在计算机上使用键盘只能在真机上输入英文。

但我发现,Pixel设备弹出的输入法可以在切换到中文输入的情况下,响应计算机上的键盘输入,正常输出中文,Android Studio自带模拟器的输入法也可以输入中文。可能是谷歌输入法的能力,Pixel即Android Studio模拟器的输入法均为谷歌输入法,其他设备上均为国产输入法,可能在这方便稍有不足。

最好是STF将键盘输入拦截,参考appium的方式,将设备的输入能力由系统控制而不是受输入法限制。

5.2.3 音频传输

目前STF虽然可以看到连接的真机的屏幕视频,但是听不到真机发出的声音,如果真机在浏览一些含有声音资源的音视频,那么用户无法听到声音,反而真机突然发出的声音可能会吓到设备管理员。

如果可以拦截真机的声音播放,考虑将STF作为一个虚拟的播放器,把声音传输到用户的计算机上。

5.3 商业化

5.3.1 数据安全性

STF设计初衷便是面向公司内部,它对所有用户的设想就是有一定可信赖的基础,在数据的安全性上具有天然的危险性,进程之间的所有内部通信都是不安全且未加密的。

要将STF二次开发并推向商业化,首先要解决的就是数据安全性问题。

5.3.2 云测平台

STF实际上就是一个内部的云测平台,基于STF可以迅速开发出一个商业化的云测平台,只需要解决数据安全性问题、更改其中的用户管理系统、完善汉化能力,就能够作为一个简单的云测平台使用了。

5.3.3 自动化测试

STF支持自动化测试的能力,所以可以将STF自动化测试提供一个商业化的版本,很多APP需要进行兼容性测试,通过足够多的设备、品牌、版本可以达到更全面的覆盖密度。