所有文件又重新提交了。你再 clone 试试,应该是环境问题
没有 我看 wda,xctestwd 代码大致类似。 理论上 oc 的应该更快些,你那里速度慢 根本原因是用的 appium,中间经过了太多过程,比如网络请求
是的,需要占两个 bundle id
录屏我也还没有实现,仅仅是个想法。
你说的 getpagesource 是哪个框架的 api? 在 44 楼发了个优化的方案,牺牲一些准确度来提升获取速度。
我测试这个是可以用,可能没公开。我当时搜 google 好像在某个 stackoverflow 里看到的。
注! 熊猫直播测试组 出品; 玩游戏关注熊猫直播
已开源了 1.0.0.1002。 尚有瑕疵,望见谅。
运行说明:
配置:
XCTestWDMonkeyController.swift
let monkey = Monkey(frame: app.frame)
monkey.addDefaultXCTestPrivateActions()
monkey.addXCTestTapAlertAction(interval: 100, application: app)
monkey.addXCTestCheckCurrentApp(interval: 10, application: app) # app后台或退出检测
//monkey.addXCTestAppLogin(interval: 50, application: app) # 登陆业务逻辑 50次事件加入1次业务关键点检查,无登陆逻辑可注释
monkey.monkeyAround()
MonkeyXCTestPrivate.swift
public func addDefaultXCTestPrivateActions() {
addXCTestTapAction(weight: 35) #随机点击事件 100-200ms/action
//addXCTestElementTapAction(weight: 10) #基于控件的点击事件 执行概率: 10/(35+10+5) 300-400ms/action
addXCTestLongPressAction(weight: 1)
addXCTestDragAction(weight: 1)
addXCTestPinchCloseAction(weight: 1)
addXCTestPinchOpenAction(weight: 1)
addXCTestRotateAction(weight: 1)
}
注!事件序列如何实现:
通过设置一个关键点 引入一个事件序列,当检测到出现关键点时插入自定义事件序列,并立即执行; 可按此原理实现自己的事件序列
public func addXCTestAppLogin(interval:Int, application:XCUIApplication) {
addAction(interval:interval){ [weak self] in
do{
let session = try XCTestWDSessionManager.singleton.checkDefaultSessionthrow() #检测到出现'登录'关键点时 插入
let root = session.application
if root != nil{
let usage = "xpath"
let tag = "//XCUIElementTypeOther[@name='登录']/XCUIElementTypeTextField"
let element = try? XCTestWDFindElementUtils.filterElement(usingText: usage, withvalue: tag, underElement: root!)
if let element = element {
if element != nil {
self?.addXCTestLoginAction(application: application) #插入一个特殊事件序列
}
else{
return
}
}
}
}catch{
return
}
}
}
public func addXCTestLoginAction(application:XCUIApplication) { #该事件为一个原子事件序列,保证事件中不会插入其他事件
addAction(){ [weak self] in
do{
let session = try XCTestWDSessionManager.singleton.checkDefaultSessionthrow()
let root = session.application
if root == nil{
return
}
let usage = "xpath"
let username = "//XCUIElementTypeOther[@name='登录']/XCUIElementTypeTextField"
let passwd = "//XCUIElementTypeOther[@name='登录']/XCUIElementTypeSecureTextField"
let button = "//XCUIElementTypeOther[@name='登录']//XCUIElementTypeStaticText[@name='登录']"
var element = try? XCTestWDFindElementUtils.filterElement(usingText: usage, withvalue: username, underElement: root!)
if let element = element {
if let element = element {
NSLog("XCTestWDSetup->loginuser find?\(String(describing: element))<-XCTestWDSetup")
let value = "1111111111"
let rect = element.wdRect()
let point = CGPoint(x:rect["x"]!,y:rect["y"]!)
let locations = [point]
let semaphore = DispatchSemaphore(value: 0)
let numberOfTaps = 1
self!.sharedXCEventGenerator.tapAtTouchLocations(locations, numberOfTaps: UInt(numberOfTaps), orientation: orientationValue) { #点击使用坐标,调用xctest私有api
semaphore.signal()
}
semaphore.wait()
注! 屏幕中控件获取如何加快速度
如果使用 app.descendants(matching: .xxx) 可以获取某种类型的控件集,但获取其中某个控件坐标时,xctest 内部会再次执行一次当前界面查找确保控件仍存在,该过程测试时间过长,超过 500ms 甚至 1s 更多。故此处进行了优化,缩短了时间 ;最终点击某个控件控制在 300-400ms
static func xpathToList(_ root:XCElementSnapshot, _ xpathQuery:String) -> [CGPoint]? {
var mapping = [String:XCElementSnapshot]()
let xml = generateXMLPresentation(root,nil,nil,defaultTopDir,&mapping)?.xml
if xml == nil
{return nil}
let tree = try? XMLDocument(string: xml!, encoding:String.Encoding.utf8) #xml tree
let nodes = tree?.xpath(xpathQuery) #筛选生成对应节点
var list = [CGPoint]()
for node in nodes! {
if mapping[node.attr("private_indexPath")!] != nil{
let x = (node.attr("x")! as NSString).floatValue
let y = (node.attr("y")! as NSString).floatValue
if (x <= 0) && (y <= 0)
{continue}
let snapshot = mapping[node.attr("private_indexPath")!]
let isvisible = try? snapshot?.isWDVisible() #仅isvisible的才视为当前可见,加入到point队列
if isvisible == nil || isvisible! == false
{continue}
let w = (node.attr("width")! as NSString).floatValue
let h = (node.attr("height")! as NSString).floatValue
let cX = Int(x + w/2)
let cY = Int(y + h/2)
let point = CGPoint(x:cX,y:cY)
if list.contains(point) == false {
list.append(point)
}
}
}
return list #最终返回所选控件对应的坐标队列集
}
注! app 退出如何检测
(坑点!!! app 切换或者闪退自身也需要时间,可能获取当前 pid 时 app 还未完成退出或切换操作)
app 退出通过三种方式检测
1) app 退出检测线程 每 500ms 执行一次, 检测耗时 50ms
public func addXCTestCheckCurrentApp(interval:Int, application:XCUIApplication) {
addCheck(interval:interval){ [weak self] in
let work = DispatchWorkItem(qos:.userInteractive){
let isRunning = application.running #获取当前是否在运行中
let current = Int(XCTestWDFindElementUtils.getAppPid()) #获取当前前台pid
if current != self?.pid || !isRunning{ #当切换到后台或已退出时,执行launch
application.launch()
self?.sleep(5)
self?.pid = Int(XCTestWDFindElementUtils.getAppPid())
}
}
DispatchQueue.main.async(execute:work)
}
}
static func getAppPid() -> Int32{
var activeApplicationElement:XCAccessibilityElement?
activeApplicationElement = (XCAXClient_iOS.sharedClient() as! XCAXClient_iOS).activeApplications().first
if activeApplicationElement == nil {
activeApplicationElement = (XCAXClient_iOS.sharedClient() as! XCAXClient_iOS).systemApplication() as? XCAccessibilityElement
}
let pid = activeApplicationElement?.processIdentifier #通过私有api 获取当前前台pid
if pid == nil{
return 0
}
return pid!
}
上述检测方式 因为使用 xcaxclient 私有 api 和 launch 只能在主线程中执行,故只能通过定时方式检测并插入高优先级队列执行。而且要尽量缩短其耗时从而不会影响其他各点击事件的执行频率
2) 低概率检测事件,获取 pid 前增加延迟
public func addXCTestTapAlertAction(interval: Int, application: XCUIApplication) {
addAction(interval: interval) { [weak self] in
usleep(2000000) #sleep 2s
let isRunning = application.running
let current = Int(XCTestWDFindElementUtils.getAppPid())
3) 最后一关把控,当 resovle 前再检测一次
func resolve() throws {
self._application.query()
let pid = self._application.processID
let activeApplicationElement = (XCAXClient_iOS.sharedClient() as! XCAXClient_iOS).activeApplications().first
let currentprocessID = activeApplicationElement?.processIdentifier #@A@
if pid != currentprocessID{ #最后把关 pid不同 则抛出异常
throw OperationError.Error
}
#@B@
self._application?.resolve()
}
此处实现目前仍存在瑕疵,如果 app 在上述@A处仍在前台,@B处却切到后台,monkey 可能 crash,原因是此时 application 已不在 _application.resolve 崩溃。但当 xcode9 发布时应该就不存在问题了,新 api 提供了 state,可以线程检测当前状态
(此崩溃仅会出现在 开启基于的控件点击)
未来后续;
1) 瑕疵修正
2) 性能监控
3) 崩溃分析
4) 控件选择子 算法
404 那个发帖时链接多了个( ,不明所以。。
这个仅仅是 api 简易说明。google 搜搜就有了
xctestwd 里已有 api 取 title
取 title 辅助定位就行。appname 会引起个问题,那个函数要重写了
已完成 基于控件的点击,解析出所有控件,随机某个控件点其中心点 。每秒大概 3-4 个 action
log 的 就记录每个 action 后 点击什么页面什么位置。
重新 launcher 就调的 XCUIApplication.launch()
但这样会重启 app,我希望能后台切回前台是最好的。
见 官方 api
https://developer.apple.com/documentation/xctest/xcuiapplication
感觉应该用 activate() 但目前只是 beta 的
自己加了个接口。
这个取得是 xml 里 节点属性 name
打个比方,会出现点击了 20 次 可能才截出 6,7 张图。 截图比较鸡肋,实际效果不如录像或者 log
截图没有细研究,目前调试看截图是异步的,跟不上 action 的速度
共勉
稍微改造下可以的。
补充一个业务流程
我理解是 用线上真实数据在生产环境上回放流量 或加倍回放。
可以的。这个链路执行的时候 ,随机会暂停。执行完随机再继续。只要设置好各个链路的触发点即可。相对于单个链路中的行为不随机,而不同链路之间也是随机的
业务流程脚本? 比如登陆吗? 如果是跑 monkey 过程中插入一些特定业务逻辑是可以的。 业务逻辑实际就是原子性的一系列点击输入。 如果是按 case 跑目前还不支持 改造会比较大 但理论上也是可以的
这工作环境略叼!
另外确实可以不需要源码,参考 wda 实现的方式,swiftmonkey 实现一个监听的 server
xcode 执行 xcuitestcase 时先启一个 server,而后再往这个端口发指令,server 接受到指令后创建一个 session 并启动待测 app,然后调起 monkey
命令行启动 可以用 xcodebuild