UNPKG

flasco_wda-driver

Version:

self wda-driver adjust from zqingr/wda-driver

405 lines (306 loc) 11.7 kB
# wda-driver [中文版](https://github.com/zqingr/wda-driver/tree/master/doc/README_CN.md) Facebook WebDriverAgent Node Client Library (not official) Most functions finished. ## Installation 1. You need to start WebDriverAgent by yourself Follow the instructions in <https://github.com/facebook/WebDriverAgent> It is better to start with Xcode to prevent CodeSign issues. But it is also ok to start WDA with command line. ``` xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination 'platform=iOS Simulator,name=iPhone 6' test ``` 2. Install wda-driver client ``` npm install --save wda-driver ``` ## TCP connection over USB (optional) You can use wifi network, it is very convinient, but not very stable enough. I found a tools named `iproxy` which can forward device port to localhost, it\'s source code is here <https://github.com/libimobiledevice/libusbmuxd> The usage is very simple `iproxy <local port> <remote port> [udid]` ## Configuration ```javascript const wda = require('wda-driver') ``` ## How to use ### Create a client ```javascript const wda = require('wda-driver') const c = new wda.Client('http://localhost:8100') // http://localhost:8100 is the default value c = wda.Client() ``` ### Client ```javascript // Show status console.log(await c.status()) // Press home button await c.home() // Hit healthcheck await c.healthcheck() // Get page source // format (str): only 'xml' and 'json' source types are supported // accessible (bool): when set to true, format is always 'json' const source = await c.source() // format XML const source = await c.source(null, true) // default false, format JSON ``` Take screenshot, only can save format png, if params is empty, it will return base64 buffer ```javascript await c.screenshot('screen.png') ``` Get information about the current app ```javascript await c.getActiveAppInfo(); ``` do actions chain ```javascript /** Perform complex touch action in scope of the current application. Touch actions are represented as lists of dictionaries with predefined sets of values and keys. Each dictionary must contain 'action' key, which is one of the following: - 'tap' to perform a single tap - 'longPress' to perform long tap - 'press' to perform press - 'release' to release the finger - 'moveTo' to move the virtual finger - 'wait' to modify the duration of the preceeding action - 'cancel' to cancel the preceeding action in the chain Each dictionary can also contain 'options' key with additional parameters dictionary related to the appropriate action. The following options are mandatory for 'tap', 'longPress', 'press' and 'moveTo' actions: - 'x' the X coordinate of the action - 'y' the Y coordinate of the action - 'element' the corresponding element instance, for which the action is going to be performed If only 'element' is set then hit point coordinates of this element will be used. If only 'x' and 'y' are set then these will be considered as absolute coordinates. If both 'element' and 'x'/'y' are set then these will act as relative element coordinates. It is also mandatory, that 'release' and 'wait' actions are preceeded with at least one chain item, which contains absolute coordinates, like 'tap', 'press' or 'longPress'. Empty chains are not allowed. The following additional options are available for different actions: - 'tap': 'count' (defines count of taps to be performed in a row; 1 by default) - 'longPress': 'duration' (number of milliseconds to hold/move the virtual finger; 500.0 ms by default) - 'wait': 'ms' (number of milliseconds to wait for the preceeding action; 0.0 ms by default) List of lists can be passed there is order to perform multi-finger touch action. Each single actions chain is going to be executed by a separate virtual finger in such case. */ 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); ``` Determine if the device is locked ```javascript await c.isLocked(); ``` Open app ```javascript const s = await c.session('com.apple.Health') console.log(await s.orientation()) await s.close() ``` For web browser like Safari you can define page whit which will be opened: ```javascript const s = await c.session('com.apple.mobilesafari', ['-u', 'https://www.google.com/ncr']) console.log(await s.orientation()) await s.close() ``` ### Session operations ```javascript // Current bundleId and sessionId console.log(s.getId(), s.getBundleId()) // One of <PORTRAIT | LANDSCAPE> console.log(await s.orientation()) // expect PORTRAIT // Change orientation // LANDSCAPE | PORTRAIT | UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT |UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN await s.orientation(orientation) // Deactivate App for some time await s.deactivate(5) // 5s // Get width and height console.log(await s.getWindowSize()) // Expect json output // For example: {'height': 736, 'width': 414} // Simulate touch await s.tap(88, 200) // Double touch await s.doubleTap(200, 200) // Simulate swipe, utilizing drag api await s.swipe(x1, y1, x2, y2, 0.5) // 0.5s await s.swipeLeft() await s.swipeRight() await s.swipeUp() await s.swipeDown() // tap hold await s.tapHold(x, y, 1.0) ``` ### Find element ```javascript // For example, expect: true or false // using id to find element and check if exists const selector = s.selector({id: "URL"}) await selector.exists() // return true or false // using id or other query conditions s.selector({id: 'URL'}) s.selector({name: 'URL'}) s.selector({text: "URL"}) // text is alias of name s.selector({nameContains: 'UR'}) s.selector({label: 'Address'}) s.selector({labelContains: 'Addr'}) s.selector({name:'URL', index: 1}) # find the second element. index starts from 0 // combines search conditions // attributes bellow can combines // :"className", "name", "label", "visible", "enabled" s.selector({className: 'Button', name: 'URL', visible: true, labelContains: "Addr"}) ``` More powerful findding method ```javascript s.selector({xpath: '//Button[@name="URL"]'}) s.selector({classChain: '**/Button[`name == "URL"`]'}) s.selector({predicate: 'name LIKE "UR*"'}) ``` ### Element operations (eg: `tap`, `scroll`, `set_text` etc...) Exmaple search element and tap ```javascript // Get first match Element object // The function get() is very important.it will return an Element object // when elements founded in 10 seconds(:default:), Element object returns const e = await s.selector({text: 'Dashboard'}).get(10) // e is elements object await e.tap() // tap element ``` Click element if exists ```javascript await s.selector({text: 'Dashboard'}).clickExists() // return immediately if not found await s.selector({text: 'Dashboard'}).clickExists(5) // wait for 5s ``` Other Element operations ```javascript // Check if elements exists console.log(await s.selector({text: 'Dashboard'}).exists()) // Find all matches elements, return Array of Element object await s.selector({className: 'Other'}).findElements() // Use index to find second element await s.selector({className: 'Other', index: 2}).exists() // Use child to search sub elements await s.selector({text: 'Dashboard'}).child({className: 'Cell'}).exists() // Default timeout is 10 seconds // But you can change by s.setTimeout(50) // do element operations await e.tap() await e.click() // alias of tap // The default keyboard must be requested await e.clearText() await e.setText("Hello world") await e.tapHold(2) // tapAndHold for 2.0s await e.scroll() // scroll to make element visiable // directions can be "up", "down", "left", "right" // swipe distance default to its height or width according to the direction await e.scroll('up', 100) // Set text await e.setText("Hello WDA") // normal usage await e.setText("Hello WDA\n") // send text with enter await e.setText("\b\b\b") // delete 3 chars // Wait element gone await s({className: 'Other'}).waitGone(10) // Swipe TODO // s(className="Image").swipe("left") // Pinch s(className="Map").pinch(2, 1) // scale=2, speed=1 s(className="Map").pinch(0.1, -1) // scale=0.1, speed=-1 (I donot very understand too) // properties (bool) await e.getAccessible() await e.getDisplayed() await e.getEnabled() await e.getVisible() await e.getAccessibilityContainer() // properties (str) 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() // Bounds return namedtuple const rect = await e.getBounds() // Rect { x: 0, y: 73, width: 375, height: 666 } rect.y // 73 ``` Alert ```javascript console.log(await s.alert().exists()) console.log(await s.alert().text()) console.log(await s.alert().text()) await s.alert().accept() // Actually do click first alert button await s.alert().dismiss() // Actually do click second alert button await s.alert().wait(5) // if alert apper in 5 second it will return true,else return false (default 20.0) await s.alert().wait() // wait alert apper in 20 second await s.alert().buttons() // example 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 | Another way to list apps installed on you phone is use `ideviceinstaller` install with `brew install ideviceinstaller` List apps with command ```sh $ ideviceinstaller -l ``` ## Reference This project is a transplant by 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)