flasco_wda-driver
Version:
self wda-driver adjust from zqingr/wda-driver
402 lines (305 loc) • 10.6 kB
Markdown
# wda-driver
Facebook WebDriverAgent的Node库
大部分功能都已经完成
## 安装
1. 你需要自己安装和启动 WebDriverAgent
可以跟着官方文档安装: <https://github.com/facebook/WebDriverAgent>
你可以从xcode里面启动调试程序
也可以直接命令行启动
```
xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination 'platform=iOS Simulator,name=iPhone 6' test
```
2. 安装wda-driver
```
npm install --save wda-driver
```
## TCP 有时候连接不上 (可选)
你可以直接用wda提供的网络地址,但是不太稳定,有时候会出现连接不上的问题
可以安装iproxy工具 <https://github.com/libimobiledevice/libusbmuxd>
开启非常简单 `iproxy <local port> <remote port> [udid]`
例如:```iproxy 8100 8100```
## 引用
```javascript
const wda = require('wda-driver')
```
## 如何使用
### 建立一个客户端
```javascript
const wda = require('wda-driver')
const c = new wda.Client('http://localhost:8100')
// http://localhost:8100 是默认值,你可以直接这样
c = wda.Client()
```
### 客户端操作
```javascript
// 查看客户端状态
console.log(await c.status())
// 点击home按钮
await c.home()
// Hit healthcheck
await c.healthcheck()
// 获取 page source
// 两个参数
// format (str): 'xml' 或 'json'
// accessible (bool): 设置为true的时候,只返回json
const source = await c.source() // 格式为 XML
const source = await c.source(null, true) // 默认 false, 格式为 JSON
```
截取屏幕,只支持png格式的图片,不传参数的话会直接返回base64的buffer
```javascript
await c.screenshot('screen.png')
```
打开 app
```javascript
const s = await c.session('com.apple.Health')
console.log(await s.orientation())
await s.close()
```
获取当前app的信息
```javascript
await c.getActiveAppInfo();
```
一次请求带链式操作
```javascript
/**
在当前应用程序的范围内执行复杂的触摸操作。
触摸操作表示为具有预定义值和键集的字典列表。
每个字典必须包含“action”键,它是以下之一:
- 'tap' 单次点击
- 'longPress' 长按
- 'press' 按下
- 'release' 松开
- 'moveTo' 移动虚拟按键
- 'wait' 链式操作中等待
- 'cancel' 取消链中的前一个动作
每个字典还可以包含“选项”键以及与相应操作相关的附加参数字典
对于'tap', 'longPress', 'press' and 'moveTo', 下面的属性是强制要求的:
- 'x' x坐标
- 'y' y坐标
- 'element' 将要执行操作的相应元素实例
如果仅设置'element',则将使用该元素的点击坐标.
如果仅设置'x'和'y',则将这些视为绝对坐标.
如果同时设置'element'和'x'/'y',则这些将作为相对元素坐标.
它也是强制性的,'release'和'wait'动作前面至少有一个链项,它包含绝对坐标,如'tap','press'或'longPress'。 不允许空链.
以下附加选项可用于不同的操作:
- 'tap': 'count' (按的次数; 默认是1)
- 'longPress': 'duration' (长按的持续时间,默认是 500ms)
- 'wait': 'ms' (等待的时长; 默认是 0ms)
上面列出来的可以在多指操作中生效,每个链式操作会对每个按点生效
*/
const actions = [
{
action: 'tap',
options: {
x: 300,
y: 100
}
},
{
action: 'wait',
options: {
ms: 700
}
},
{
action: 'tap',
options: {
x: 300,
y: 100
}
}
];
await session.chainOperation(actions);
```
判断当前设备是否锁屏
```javascript
await c.isLocked();
```
浏览器可以这样带网址的打开:
```javascript
const s = await c.session('com.apple.mobilesafari', ['-u', 'https://www.google.com/ncr'])
console.log(await s.orientation())
await s.close()
```
### 对话操作
```javascript
// 当前的 bundleId 和 sessionId
console.log(s.getId(), s.getBundleId())
// <PORTRAIT 或者 LANDSCAPE>
console.log(await s.orientation()) // PORTRAIT
// 改变方向
// LANDSCAPE | PORTRAIT | UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT |UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN
await s.orientation(orientation)
// 让app进入后台5秒钟
await s.deactivate(5) // 5s
// 获取高度和宽度
console.log(await s.getWindowSize())
// 返回例子: {'height': 736, 'width': 414}
// 根据坐标点击屏幕
await s.tap(88, 200)
// 双击屏幕
await s.doubleTap(200, 200)
// 滑动相关的方法
await s.swipe(x1, y1, x2, y2, 0.5) // 0.5s
await s.swipeLeft()
await s.swipeRight()
await s.swipeUp()
await s.swipeDown()
// 长按屏幕
await s.tapHold(x, y, 1.0)
```
### 查找元素
```javascript
// 用获取的session的selector方法查找元素
const selector = s.selector({id: "URL"})
await selector.exists() // return true or false
// 用id等查询元素
s.selector({id: 'URL'})
s.selector({name: 'URL'})
s.selector({text: "URL"}) // text是name的别名
s.selector({nameContains: 'UR'})
s.selector({label: 'Address'})
s.selector({labelContains: 'Addr'})
s.selector({name:'URL', index: 1}) // 查找第二个name为URL的元素
// 合并查询
// 属性可以选择一下元素
// :"className", "name", "label", "visible", "enabled"
s.selector({className: 'Button', name: 'URL', visible: true, labelContains: "Addr"})
```
更多牛逼的查询方式
```javascript
s.selector({xpath: '//Button[@name="URL"]'})
s.selector({classChain: '**/Button[`name == "URL"`]'})
s.selector({predicate: 'name LIKE "UR*"'})
```
### 元素操作 (例如: `tap`, `scroll`, `set_text` 等...)
例子:查找一个元素然后点击他
```javascript
// 查找第一个text为Dashboard的元素
// function get() 非常重要.执行后才会返回一个元素对象
// 如果元素在10(默认)秒内查找到了, 就会返回一个元素对象
const e = await s.selector({text: 'Dashboard'}).get(10) // e 是一个元素对象
await e.tap() // 点击元素
```
如果元素存在点击元素
```javascript
await s.selector({text: 'Dashboard'}).clickExists() // 如果不存在会立刻返回
await s.selector({text: 'Dashboard'}).clickExists(5) // 等待5秒
```
其它的元素查找
```javascript
// 查找元素是否存在
console.log(await s.selector({text: 'Dashboard'}).exists())
// 查找所有匹配的元素
await s.selector({className: 'Other'}).findElements()
// 查找第二个元素
await s.selector({className: 'Other', index: 2}).exists()
// 查找子元素
await s.selector({text: 'Dashboard'}).child({className: 'Cell'}).exists()
// 默认是10秒
// 但是你可以自己设置
s.setTimeout(50)
// 元素操作
await e.tap()
await e.click() // tap的别名
// 必须是系统默认键盘,搜狗测试的不行
await e.clearText()
await e.setText("Hello world")
await e.tapHold(2) // 长按2秒
await e.scroll() // 滚动到元素可见出
// 方向可以使 "up", "down", "left", "right"
// 也可以指定移动的距离
await e.scroll('up', 100)
// 发送文字
await e.setText("Hello WDA") // 一般用法
await e.setText("Hello WDA\n") // 发送回车
await e.setText("\b\b\b") // 删除三个字符
// 等待元素小时
await s({className: 'Other'}).waitGone(10)
// Swipe TODO
// s(className="Image").swipe("left")
// 宋芳
s(className="Map").pinch(2, 1) // scale=2, speed=1
s(className="Map").pinch(0.1, -1) // scale=0.1, speed=-1
// 获取属性 (boolean)
await e.getAccessible()
await e.getDisplayed()
await e.getEnabled()
await e.getVisible()
await e.getAccessibilityContainer()
// 或许属性 (字符串)
await e.getId()
await e.getLabel()
await e.getClassName()
await e.getText()
await e.getName()
await e.getDisplayed()
await e.getEnabled()
await e.getValue()
await e.getValue()
// 返回元素的边界
const rect = await e.getBounds() // Rect { x: 0, y: 73, width: 375, height: 666 }
rect.y // 73
```
警告框
```javascript
console.log(await s.alert().exists())
console.log(await s.alert().text())
console.log(await s.alert().text())
await s.alert().accept() // 其实点击的左侧按钮
await s.alert().dismiss() // 其实点击的第二个按钮
await s.alert().wait(5) // 等待5秒弹框出现
await s.alert().wait() // 默认等待20秒弹窗出现
await s.alert().buttons()
// 例如返回 return: ["设置", "好"]
await s.alert().click('好')
```
## iOS Build-in Apps
**苹果自带应用**
| Name | Bundle ID |
| ----------- | ---------------------------------------- |
| iMovie | com.apple.iMovie |
| Apple Store | com.apple.AppStore |
| Weather | com.apple.weather |
| 相机Camera | com.apple.camera |
| iBooks | com.apple.iBooks |
| Health | com.apple.Health |
| Settings | com.apple.Preferences |
| Watch | com.apple.Bridge |
| Maps | com.apple.Maps |
| Game Center | com.apple.gamecenter |
| Wallet | com.apple.Passbook |
| 电话 | com.apple.mobilephone |
| 备忘录 | com.apple.mobilenotes |
| 指南针 | com.apple.compass |
| 浏览器 | com.apple.mobilesafari |
| 日历 | com.apple.mobilecal |
| 信息 | com.apple.MobileSMS |
| 时钟 | com.apple.mobiletimer |
| 照片 | com.apple.mobileslideshow |
| 提醒事项 | com.apple.reminders |
| Desktop | com.apple.springboard (Start this will cause your iPhone reboot) |
**第三方应用 Thirdparty**
| Name | Bundle ID |
| ------ | --------------------- |
| 腾讯QQ | com.tencent.mqq |
| 微信 | com.tencent.xin |
| 部落冲突 | com.supercell.magic |
| 钉钉 | com.laiwang.DingTalk |
| Skype | com.skype.tomskype |
| Chrome | com.google.chrome.ios |
另一个获取你手机里面app列表的方法是用 `ideviceinstaller`
安装 `brew install ideviceinstaller`
显示列表
```sh
$ ideviceinstaller -l
```
## Reference
这个项目移植的python项目 https://github.com/openatx/facebook-wda
Source code
- [Router](https://github.com/facebook/WebDriverAgent/blob/master/WebDriverAgentLib/Commands/FBElementCommands.m#L62)
- [Alert](https://github.com/facebook/WebDriverAgent/blob/master/WebDriverAgentLib/Commands/FBAlertViewCommands.m#L25)
## DESIGN
[DESIGN](DESIGN.md)
## LICENSE
[MIT](LICENSE)