我是一名iOS开发者,我是一名非计算机专业出生的程序员。
如果学习很幸苦,那就尝试无知的代价
STF (or Smartphone Test Farm) is a web application for debugging smartphones, smartwatches and other gadgets remotely, from the comfort of your browser.
简单来说,就是可以在浏览器上远程操作真机的解决方案,是由 "Device Farmer" 这个组织提供。本身 stf 不支持 iOS 设备,所以 “Device Framer” 又单独提供了 “stf_ios_support” 项目,本文讲述该项目部署操作的过程以及遇到的问题。
实际上,stf_ios_support 真机操作也是基于 WebDriverAgent 项目实现,基本想要远程操控 iOS 设备,都依赖 WebDrviverAgent 项目。由于国内的 iPhone 手机端口封闭,所以外部无法直接访问,因此依赖 wdaproxy 将手机端口映射,PC 就可以直接通过http://localhost:7000/status
来访问、操作对应的设备。而 stf_ios_support 实际上就是一个 WebSocket 服务器,用来监听、设备的连接,和传输 STF Server 发过来的指令(简单理解,就是包了一层 Proxy)。当然还有最重要的,真机画面是通过 ios_video_enabler、ffmpeg 实现.
检出 stf_ios_support
目录到 Mac 本机目录,并 cd
到 stf_ios_support
目录,执行 ./init.sh
文件,脚本执行的前提需要先安装好 Xcode 并将开发者信息配置正确(这里不讲述 Xcode 如何配置,默认你已经配置成功),如果网络不好的建议科学上网,否则很多组件会拉不下来。
git clone https://github.com/DeviceFarmer/stf_ios_support.git;
cd stf_ios_support
./init.sh
如果成功,会显示如下信息:
zhudezhen@zhudezhendeMacBook-Pro stf_ios_support % ./init.sh
Xcode 13.0 installed
jq => version 1.6
graphicsmagick => version 1.3.36
zeromq => version 4.3.4
protobuf => version 3.17.3
yasm => version 1.3.0_2
pkg-config => version 0.29.2_3
carthage => version 0.38.0
automake => version 1.16.3_1
autoconf => version 2.71
libtool => version 2.4.6_3
wget => version 1.21.1
go => version 1.16.5
node@12 => version 12.22.3
libsodium => version 1.0.18_1
czmq => version 4.2.0
jpeg-turbo => version 2.1.0
nanomsg => version 1.1.5
libgcrypt => version 1.9.3
gnutls => version 3.6.16
mobiledevice => version 2.0.0
libplist - HEAD already installed
libplist - installed HEAD is version 2.2.1 ( ==2.2.1 )
libusbmuxd - HEAD already installed
libusbmuxd - installed HEAD is version 2.0.3 ( ==2.0.3 )
zhudezhen@zhudezhendeMacBook-Pro stf_ios_support %
首先,需要安装 Docker DeskTop 客户端容器,安装完成后,确保 docker 容器配置正确。
然后,复制 stf_ios_support
目录下的 server
文件夹,到另外一个目标目录(目的是区分下,不容易造成混淆,不复制也没问题),cd 到目标目录 server
中的 cert
下,执行 gencert.sh
脚本,该服务就是 Docker 下的 STF 服务器。
cp stf_ios_support/server xxxx/xxxx/test/;
cd xxxx/xxxx/test/server/cert/;
./gencert.sh
显示如下信息,完成自签名证书:
zhudezhen@zhudezhendeMacBook-Pro cert % ./gencert.sh
Generating a 2048 bit RSA private key
.....................+++
...............................................................+++
writing new private key to 'server.key'
-----
zhudezhen@zhudezhendeMacBook-Pro cert %
然后,修改目录文件夹 server/.env
文件,将 PUBLIC_IP
、HOSTNAME
改为本机 ip,例如:这里的 192.168.199.191
PUBLIC_IP=192.168.199.191
SECRET=secret
RETHINKDB_PORT_28015_TCP=tcp://rethinkdb:28015
HOSTNAME=192.168.199.191
STF_IMAGE=livxtrm/devicefarmer:latest
然后,切回到目标文件夹 server
根目录,执行 docker-compose up
命令(科学上网),向 Docker 容器中,安装 STF 服务器,命令行开始,等待完成即可。
zhudezhen@zhudezhendeMacBook-Pro server % docker-compose up
Creating network "server_default" with the default driver
Creating volume "server_rethinkdb" with default driver
Creating volume "server_storage-temp" with default driver
Pulling rethinkdb (rethinkdb:2.3)...
2.3: Pulling from library/rethinkdb
2746a4a261c9: Download complete
4c1d20cdee96: Download complete
0d3160e1d0de: Download complete
c8e37668deea: Download complete
b7d1bf5200eb: Download complete
f6b24861af7c: Download complete
677900de5c00: Download complete
7c6f2faef424: Download complete
安装成功显示如下。
注意:如果容器中有任何服务起不来,建议将容器的内容(包括:Containers/Apps、Images、Volumes),全部删除,然后,重新执行 docker-compose up 命令。
这时候,你已经可以通过浏览器访问 http://192.168.199.191
查看 STF 服务,然后你会发现,浏览器访问不了,如下:
你需要将目标文件夹中的 server/cert/server.crt
导入进系统的“登陆钥匙串”中,并修改为“始终信任”。
这时候,你刷新浏览器,会发现高级
中的选项,多了个熟悉的操作,点击继续前往192.168.199.191(不安全)
,就可以访问了。(账号/邮箱随意输入)
回到 stf_ios_support 目录中,复制 config.json.example 到 config.json ,编辑里面的信息,因为是 json 文件,所以里面的注释要全部移除,有两段配置信息,上面一段 json 是最简单配置的事例,下面一段是完整配置事例,我们复制下面一段即可。
需要修改 json 字段中的 "xcode_dev_team_id"、 “stf”、“install”、“bin_paths” 中的信息,其他不用修改,如下:
{
...
// iOS 开发者组织的 teamid,这个开发者证书的组织id有关,可以前往要是串查看
"xcode_dev_team_id": "xxxx",
"stf": {
// STF 服务所在服务的 ip 地址,就是上文 server/.env 环境配置的地址
"ip": "192.168.199.191",
"hostname": "192.168.199.191"
},
"install": {
// 修改为当前 stf_ios_support 中的目录地址即可
"root_path": "xxxxx/xxxx/stf_ios_support",
"config_path": "xxxxx/xxxx/stf_ios_support/config.json",
"set_working_dir": false
},
"bin_paths": {
// 这里需要修改 osx_ios_video_enabler => video_enabler
"video_enabler": "bin/video_enabler",
}
}
配置完成后,在命令行执行,make
命令,开始编译 stf_ios_support
服务器(同样科学上网),等待编译完成,信息如下:
zhudezhen@zhudezhendeMacBook-Pro stf_ios_support % make
GIT_COMMIT="12aabb5d3ac0b9b6fce56de4a4c7368bff695f27" GIT_DATE="1619067214" GIT_REMOTE="https://github.com/DeviceFarmer/stf_ios_support.git" EASY_VERSION="1.0" /Applications/Xcode13-beta.app/Contents/Developer/usr/bin/make -C coordinator
go get
go get .
go build -o ../bin/coordinator -ldflags "-X main.GitCommit=12aabb5d3ac0b9b6fce56de4a4c7368bff695f27 -X main.GitDate=1619067214 -X main.GitRemote=https://github.com/DeviceFarmer/stf_ios_support.git -X main.EasyVersion=1.0" .
git clone https://github.com/nanoscopic/ios_video_stream.git repos/ios_video_stream
Cloning into 'repos/ios_video_stream'...
remote: Enumerating objects: 1923, done.
remote: Counting objects: 100% (1923/1923), done.
remote: Compressing objects: 100% (649/649), done.
Receiving objects: 29% (574/1923), 11.18 MiB | 1.09 MiB/s s
这时候,其实编译无法成功,首先你会遇到第 1 个报错,信息如下:
o build -o ios_video_pull .
go: github.com/nanoscopic/ios_video_pull: package github.com/google/gousb imported from implicitly required module; to add missing requirements, run:
go get github.com/google/gousb@v0.0.0-20190812193832-18f4c1d8a750
make[1]: *** [ios_video_pull] Error 1
make: *** [repos/ios_video_pull/ios_video_pull] Error 2
这个报错是因为,ios_video_pull 的配置文件中缺少了 gousb 模块,需要修改
repos/ios_video_pull/go.mod
文件,内容如下:
module github.com/nanoscopic/ios_video_pull
go 1.14
require (
github.com/danielpaulus/quicktime_video_hack v0.0.0-20200514194616-c4570b6b687c
// ******* BEGIN 添加这一行即可 ******
github.com/google/gousb v0.0.0-20190812193832-18f4c1d8a750
// ******* END 添加这一行即可 ******
// indirect
github.com/nanomsg/mangos v2.0.0+incompatible
github.com/sirupsen/logrus v1.6.0
go.nanomsg.org/mangos/v3 v3.0.1
nanomsg.org/go/mangos/v2 v2.0.8 // indirect
)
然后,重新执行 make
命令,就继续往下走了,然后又会遇到第 2 个报错:
go build .
cp repos/wdaproxy/wdaproxy bin/wdaproxy
go get github.com/fsnotify/fsnotify
go get github.com/sirupsen/logrus
go build view_log.go
view_log.go:13:5: no required module provides package github.com/fsnotify/fsnotify: go.mod file not found in current directory or any parent directory; see 'go help modules'
view_log.go:14:5: no required module provides package github.com/sirupsen/logrus: go.mod file not found in current directory or any parent directory; see 'go help modules'
make: *** [view_log] Error 1
zhudezhen@zhudezhendeMacBook-Pro stf_ios_support %
意思是,go.mod 文档找不到,这时候需要执行如下命令:
go mod init view_log.go;
go mod tidy ;
继续 make
,又继续往下走了...,接下来又会遇到 WebDriverAgent 的报错:
/bin/sh: ./Scripts/bootstrap.sh: No such file or directory
make: *** [repos/WebDriverAgent/Carthage/Checkouts/RoutingHTTPServer/Info.plist] Error 127
这是因为 WebDriverAgent 最新的项目你不用 bootstrap.sh 了,直接可以用,所以我们需要手动更改配置、并修改脚本文件。
首先,cd 到 stf_ios_support/repos/WebDriverAgent
的目录,使用 Xcode IDE 打开这个项目
修改 Target 为 WebDriverAgentLib 的 BundleId 属性,以及自己可用的描述文件。
修改 Target 为 WebDriverAgentRunner 的 BundleId,以及自己可用的描述文件,BundleId 和上文的值保持一致。
最后,可以修改 Target 目标,和构建的目标真机,可以按 command+U
进行测试。
有如下信息说明成功了。
然后,我们回到 stf_ios_support 上,找到 stf_ios_support/makefile
文件,找到下这行代码:
repos/WebDriverAgent/Carthage/Checkouts/RoutingHTTPServer/Info.plist: | repos/WebDriverAgent
cd repos/WebDriverAgent && ./Scripts/bootstrap.sh
修改为如下:
repos/WebDriverAgent/Carthage/Checkouts/RoutingHTTPServer/Info.plist: | repos/WebDriverAgent
pwd
然后,继续 make
...,基本上到这里,就 ok 了,如下信息:
Signing Identity: "Apple Development: 德振 朱 (5LLQQ4S25F)"
Provisioning Profile: "epoint_develop"
(99f3dc52-90a2-4a39-ba42-2d4f14987b3e)
/usr/bin/codesign --force --sign A7EAFF73719AECC112E9E746E0659D08F20BD121 --entitlements /Users/zhudezhen/Library/Developer/Xcode/DerivedData/WebDriverAgent-dvupaxwdbymikpdqfnltimkvxrvr/Build/Intermediates.noindex/WebDriverAgent.build/Debug-iphoneos/WebDriverAgentRunner.build/WebDriverAgentRunner.xctest.xcent --timestamp\=none --generate-entitlement-der /Users/zhudezhen/Library/Developer/Xcode/DerivedData/WebDriverAgent-dvupaxwdbymikpdqfnltimkvxrvr/Build/Products/Debug-iphoneos/WebDriverAgentRunner-Runner.app
/Users/zhudezhen/Library/Developer/Xcode/DerivedData/WebDriverAgent-dvupaxwdbymikpdqfnltimkvxrvr/Build/Products/Debug-iphoneos/WebDriverAgentRunner-Runner.app: replacing existing signature
** TEST BUILD SUCCEEDED **
zhudezhen@zhudezhendeMacBook-Pro stf_ios_support %
编译的产出物如下:
插上你的设备,然后执行如下命令:
stf_ios_support/run;
控制台会有如下,输出信息:
zhudezhen@zhudezhendeMacBook-Pro stf_ios_support % ./run
INFO[0000] auto network interface set; using en0 interface_name=en0 type=default_iface
INFO[0000] Process start - device_trigger binary=bin/ios-deploy proc=device_trigger type=proc_start
INFO[0000] Process start - stf_ios_provider binary=/usr/local/opt/node@12/bin/node client_hostname=zhudezhendeMacBook-Pro.local client_ip=192.168.199.191 location=macmini/zhudezhendeMacBook-Pro.local proc=stf_ios_provider server_hostname=192.168.199.191 server_ip=192.168.199.191 type=proc_start
INFO[0000] Device object created dev_ios_port=9240 dev_name=iPhone15 dev_uuid="***401E" type=devd_create usbmuxd_port=9920 vid_port=8000 vnc_port=5901 wda_port=8100
INFO[0000] Device connected dev_name=iPhone15 dev_uuid="***401E" type=dev_connect
INFO[0000] Process start - ivf binary=bin/ivf_pull outSpec="tcp://127.0.0.1:7879" proc=ivf type=proc_start uuid="***401E"
INFO[0000] Process start - video_enabler binary=bin/video_enabler proc=video_enabler type=proc_start uuid="***401E"
INFO[0000] Process start - ios_video_stream binary=bin/ios_video_stream port=8000 proc=ios_video_stream pullSpec="tcp://127.0.0.1:7879" tunName=en0 type=proc_start uuid="***401E"
WARN[0000] Process end - ivf proc=ivf type=proc_end uuid="***401E"
INFO[0000] Process start - ivf binary=bin/ivf_pull outSpec="tcp://127.0.0.1:7879" proc=ivf type=proc_start uuid="***401E"
INFO[0000] Device disconnected dev_name=iPhone15 dev_uuid="***401E" type=dev_disconnect
String to parse:{"type":"frame1","width":1170,"height":2532,"clickScale":1000,"uuid":"00008101-001945562001401E"
这时候,我们看到 stf_ios_support,似乎找到设备了,但是在STF网页上依旧找不到设备。
这是因为,wda 没有启动,所以没有设备,为什么没有启动呢?你可以把 stf_ios_support/config.json
中的 video.enable
修改为 false,重新尝试下:
"video": {
"enabled": false,
},
重新启动服务 stf_ios_support/run
,惊奇地发现:
dezhendeMacBook-Pro.local proc=stf_ios_provider server_hostname=192.168.199.191 server_ip=192.168.199.191 type=proc_start
INFO[0001] Device object created dev_ios_port=9240 dev_name=iPhone15 dev_uuid="***401E" type=devd_create usbmuxd_port=9920 vid_port=8000 vnc_port=5901 wda_port=8100
INFO[0001] Device connected dev_name=iPhone15 dev_uuid="***401E" type=dev_connect
trying to get ios version
INFO[0003] Process start - wdaproxy --iosDeploy=bin/ios-deploy binary=../wdaproxy iosVersion=15.0 proc=wdaproxy type=proc_start uuid="***401E" wdaPort=8100
INFO[0007] WDA Running proc=wdaproxy type=wda_started uuid="***401E"
Status response: {"value":{"build":{"productBundleIdentifier":"com.facebook.WebDriverAgentRunner","time":"Jul 17 2021 14:34:00"},"device":{"name":"whocares","udid":"00008101-001945562001401E"},"ios":{"ip":"169.254.124.5"},"message":"WebDriverAgent is ready to accept commands","os":{"name":"iOS","sdkVersion":"15.0","testmanagerdVersion":28,"version":"15.0"},"ready":"true","state":"success"},"status":0}
INFO[0010] Fetched WDA session id=AD259002-A2CD-4C72-AF56-B38F42FB60B4 type=wda_session uuid="***401E"
window size response: {
"value" : {
"width" : 390,
"height" : 844
},
"sessionId" : "AD259002-A2CD-4C72-AF56-B38F42FB60B4"
}
INFO[0010] Fetched device screen dimensions height=844 type=device_dimensions uuid="***401E" width=390
INFO[0010] Process start - stf_device_ios binary=/usr/local/opt/node@12/bin/node clickHeight=844 clickScale=1000 clickWidth=390 client_ip=192.168.199.191 device_name=iPhone15 frame_server="ws://192.168.199.191:8000/echo" node_port=9240 proc=stf_device_ios server_host=192.168.199.191 server_ip=192.168.199.191 stream_height=0 stream_width=0 type=proc_start uuid="***401E" video_port=8000 vnc_scale=2
WDA 居然启动了,于是在浏览器器上,我们发现了设备(居然还是 Android 的图标,真丑):
我们点击设备进去,发现黑屏,没有内容,啥也没有(T_T)
但是进行点击 home 按键、手势拖拽操作
时,对应的设备也会进行操作,想必肯定和之前的 “video.enable" 有关,于是将参数,改回 true 之后,重新运行 stf_ios_support,发现 wda 又启不来,连不上 STF(T_T),无奈,我去看了 go 代码,反复打日志、重新编译,在 stf_ios_support/coordinator/coordiantor.go
文件中,找到一处可疑代码:
if !o.config.Video.Enabled ||
( o.devd.okVidInterface == true && o.devd.okFirstFrame == true ) ||
videoMethod == "app" {
o.devd.wdaStarted = true
time.Sleep( time.Second * 2 )
fmt.Printf("trying to get ios version\n")
log.WithFields( log.Fields{
"type": "ios_version",
"dev_name": o.devd.name,
"dev_uuid": uuid,
"ios_version": o.devd.iosVersion,
} ).Debug("IOS Version")
proc_wdaproxy( o, devEventCh, false )
}
全文只有这里在执行启动 wda,用了!o.config.Video.Enable
去判断,为什么?此时此刻,我不想分析作者的用意了,于是我将代码修改为如下:
// if !o.config.Video.Enabled ||
// ( o.devd.okVidInterface == true && o.devd.okFirstFrame == true ) ||
// videoMethod == "app" {
o.devd.wdaStarted = true
time.Sleep( time.Second * 2 )
fmt.Printf("trying to get ios version\n")
log.WithFields( log.Fields{
"type": "ios_version",
"dev_name": o.devd.name,
"dev_uuid": uuid,
"ios_version": o.devd.iosVersion,
} ).Debug("IOS Version")
proc_wdaproxy( o, devEventCh, false )
// }
然后执行如下命令:
rm ./bin/coordinator && make && ./run
这时候,我们发现,视频插件和 wda 一起启动了,但是依旧看不到画面,这里是因为 iOS 的画面是依赖视频流的所以需要在设备上挂起视频流服务,怎么操作呢?
需要做如下操作,验证是否连接到设备,可以使用如下命令:
stf_ios_support/bin/ivf_pull list;
如果什么也没有输出,说名设备没有链接上,这时候,可以选择系统的“quicktime-> 新建影片录制 -> 选择 iPhone 设备”,这时候再执行命令,我们发现会有如下输出:
zhudezhen@zhudezhendeMacBook-Pro stf_ios_support % ./bin/ivf_pull list
--Device--
Name:amp
UDID:b5457eeaa93eb8a3c6ce3fee90c5c9f75251593b
zhudezhen@zhudezhendeMacBook-Pro stf_ios_support %
然后依次,输入如下命令,信任设备:
// 设备信任
idevicepair pair
// 确认设备视频拉取服务的 pid
./bin/ios_video_pull -devices -decimal
// 重置设备的视频流
./bin/devreset [decimal product ID] 1452
然后重新执行 stf_ios_support/run
,这一次效果如下:
画面也有了,同时也能远程操作,到这里,流程算是全部走完了。
Creating server_rethinkdb_1 ...
Creating server_auth_1 ...
Creating server_storage-temp_1 ... error
Creating server_rethinkdb_1 ... done
Creating server_auth_1 ... done
Creating server_triproxy_1 ... done
Creating server_dev-triproxy_1 ... done
Creating server_migrate_1 ... done
Creating server_reaper_1 ... done
Creating server_processor_1 ... done
Creating server_api_1 ... done
ERROR: for storage-temp Cannot start service storage-temp: OCI runtime create failed: invalid mount {Destination:data Type:bind Source:/var/lib/docker/volumes/af0f503b3f078ce72a2b09a66ca00da675709239e3c4f67bcf42575e44aa511d/_data Options:[rbind]}: mount destination data not absolute: unknown
ERROR: Encountered errors while bringing up the project.
需要修改目标目录 server/storage-temp/Dockerfile
文件中的 VOLUME 中的值为绝对目录,然后清理 Docker
中的环境,重新执行 docker-compose up
命令,如下:
FROM livxtrm/devicefarmer:latest
USER root
RUN mkdir data && chown stf:stf data
USER stf
VOLUME ["/Users/zhudezhen/Desktop/xxxx/xxx/基于STF的iOS远程真机器控制部署指南/test/server/storage-temp/data"]
测试下来,对 iOS15 的设备,链接不是很稳定,经常断开,wda 起不来。
连接上之后,可以关掉的。
需要前往“活动监视器”,找到 coordinator 服务,强制关闭即可。