From 273d258f088a849e2fc1cb8e6a48997a7818336e Mon Sep 17 00:00:00 2001 From: Jonathan Lipps Date: Fri, 3 May 2013 10:59:44 -0700 Subject: [PATCH] move other wiki files to codebase/docs --- docs/gestures.md | 179 +++++++++++++++++++++++++++++++++++++++++++ docs/hybrid.md | 40 ++++++++++ docs/ios-deploy.md | 64 ++++++++++++++++ docs/real-devices.md | 28 +++++++ docs/style-guide.md | 102 ++++++++++++++++++++++++ 5 files changed, 413 insertions(+) create mode 100644 docs/gestures.md create mode 100644 docs/hybrid.md create mode 100644 docs/ios-deploy.md create mode 100644 docs/real-devices.md create mode 100644 docs/style-guide.md diff --git a/docs/gestures.md b/docs/gestures.md new file mode 100644 index 00000000000..13b4f489ee9 --- /dev/null +++ b/docs/gestures.md @@ -0,0 +1,179 @@ +Automating mobile gestures +========================== + +While the Selenium WebDriver spec has support for certain kinds of mobile interaction, its parameters are not always easily mappable to the functionality that the underlying device automation (like UIAutomation in the case of iOS) provides. To that end, Appium augments the WebDriver spec with extra commands and parameters for mobile gestures: + +* **tap** (on screen or on element) with options: + * how many fingers + * how long to tap + * how many taps + * where precisely to tap on the screen or element. +* **flick** (on screen or on element) with options: + * how many fingers + * where to start the flick on screen or element + * where to end the flick on screen or element +* **swipe/drag** (on screen or on element) with options: + * how many fingers + * how long the swipe/drag takes in seconds + * where to start the swipe on screen or element + * where to end the swipe on screen or element + +## JSON Wire Protocol server extensions +Here are the endpoints with which we have implemented these additions to the spec. + +**Note on coordinates:** all the X and Y parameters listed below can be used in two ways. If they are between 0 and 1 (e.g., 0.5), they are taken to be percentage of screen or element size. In other words, `{x: 0.5, y: 0.25}` means a coordinate that is 50% from the left side of the screen/element, and 25% from the top of the screen/element. If the values are greater than 1, they are taken as pixels. So, `{x: 100, y: 300}` means a coordinate that is 100 pixels from the left and 300 from the top of the screen/element. + +**Note on performing actions on screen vs elements:** These methods all take an optional `element` parameter. If present, this is taken to be the ID of an element which has already been retrieved. So in this case, the coordinates will be taken to refer to the rectangle of that element only. So `{x: 0.5, y: 0.5, element: '3'}` means "the exact middle point of the element with ID '3'". + +* `POST session/:sessionId/touch/tap` - perform a tap on the screen or an element + * URL Parameter: sessionId of session to route to + * JSON parameters: + * `tapCount` (optional, default `1`): how many times to tap + * `touchCount` (optional, default `1`): how many fingers to tap with + * `duration` (optional, default `0.1`): how long (in seconds) to tap + * `x` (optional, default `0.5`): x coordinate to tap (in pixels or relative units) + * `y` (optional, default `0.5`): y coordinate to tap (in pixels or relative units) + * `element` (optional): ID of element to scope this command to +* `POST session:/sessionId/touch/flick_precise` - perform a flick on the screen or an element + * URL Parameter: sessionId of session to route to + * JSON parameters: + * `touchCount` (optional, default `1`): how many fingers to flick with + * `startX` (optional, default `0.5`): x coordinate where flick begins (in pixels or relative units) + * `startY` (optional, default `0.5`): y coordinate where flick begins (in pixels or relative units) + * `endX` (required): x coordinate where flick ends (in pixels or relative units) + * `endY` (required): y coordinate where flick ends (in pixels or relative units) + * `element` (optional): ID of element to scope this command to +* `POST session:/sessionId/touch/swipe` - perform a swipe/drag on the screen or an element + * URL Parameter: sessionId of session to route to + * JSON parameters: + * `touchCount` (optional, default `1`): how many fingers to flick with + * `startX` (optional, default `0.5`): x coordinate where swipe begins (in pixels or relative units) + * `startY` (optional, default `0.5`): y coordinate where swipe begins (in pixels or relative units) + * `endX` (required): x coordinate where swipe ends (in pixels or relative units) + * `endY` (required): y coordinate where swipe ends (in pixels or relative units) + * `duration` (optional, default `0.8`): time (in seconds) to spend performing the swipe/drag + * `element` (optional): ID of element to scope this command to + +## Alternative access method +Extending the JSON Wire Protocol is great, but it means that the various WebDriver language bindings will have to implement access to these endpoints in their own way. Naturally, this will take different amounts of time depending on the project. We have instituted a way to get around this delay, by using `driver.execute()` with special parameters. + +`POST session/:sessionId/execute` takes two JSON parameters: + * `script` (usually a snippet of javascript) + * `args` (usually an array of arguments passed to that snippet in the javascript engine) + +In the case of these new mobile methods, `script` must be one of: + * `mobile: tap` + * `mobile: flick` + * `mobile: swipe` +(The `mobile:` prefix allows us to route these requests to the appropriate endpoint). + +And `args` will be an array with one element: a Javascript object defining the parameters for the corresponding function. So, let's say I want to call `tap` on a certain screen position. I can do so by calling `driver.execute` with these JSON parameters: + +```json +{ + "script": "mobile: tap", + "args": [{ + "x": 0.8, + "y": 0.4 + }] +} +``` +In this example, our new `tap` method will be called with the `x` and `y` params as described above. + +## Code examples +Here are some examples of how to call these methods using the `execute` strategy in [wd](http://github.com/admc/wd): + +```js +driver.elementsByTagName('tableCell', function(err, els) { + var tapOpts = { + x: 150 // in pixels from left + , y: 30 // in pixels from top + , element: els[4].value // the id of the element we want to tap + }; + driver.execute("mobile: tap", [tapOpts], function(err) { + // continue testing + }); +}); +``` + +```js +// options for a 2-finger flick from the center of the screen to the top left +var flickOpts = { + endX: 0 + , endY: 0 + , touchCount: 2 +}; +driver.execute("mobile: flick", [flickOpts], function(err) { + // continue testing +}); +``` +```js +// options for a slow swipe from the right edge of the screen to the left +var swipeOpts = { + startX: 0.95 + , startY: 0.5 + , endX: 0.05 + , endY: 0.5 + , duration: 1.8 +}; +driver.execute("mobile: swipe", [swipeOpts], function(err) { + // continue testing +}); +``` +The equivalent operations in Java are as follows: + +```java +WebElement row = driver.findElements(By.tagName("tableCell")).get(4); +JavascriptExecutor js = (JavascriptExecutor) driver; +HashMap tapObject = new HashMap(); +tapObject.put("x", 150); // in pixels from left +tapObject.put("y", 30); // in pixels from top +tapObject.put("element", ((RemoteWebElement) row).getId()); // the id of the element we want to tap +js.executeScript("mobile: tap", tapObject); +``` +```java +JavascriptExecutor js = (JavascriptExecutor) driver; +HashMap flickObject = new HashMap(); +flickObject.put("endX", 0); +flickObject.put("endY", 0); +flickObject.put("touchCount", 2); +js.executeScript("mobile: flick", flickObject); +``` + +```java +JavascriptExecutor js = (JavascriptExecutor) driver; +HashMap swipeObject = new HashMap(); +swipeObject.put("startX", 0.95); +swipeObject.put("startY", 0.5); +swipeObject.put("endX", 0.05); +swipeObject.put("endY", 0.5); +swipeObject.put("duration", 1.8); +js.executeScript("mobile: swipe", swipeObject); +``` + +The equivalent of tap in python is: +```python +driver.execute_script("mobile: tap",{"touchCount":"1","x":"0.9","y":"0.8","element":"element_id"}) +``` + +An example of tapping at 150, 30 in Ruby: + +```ruby +@driver.execute_script 'mobile: tap', :x => 150, :y => 30 +``` + +Click the Sign In button using element id in Ruby: + +```ruby +b = @driver.find_element :name, 'Sign In' +@driver.execute_script 'mobile: tap', :element => b.ref +``` + +Tap on coordinates in C#: + +```C# +Dictionary coords = new Dictionary(); +coords.Add("x", 12); +coords.Add("y", 12); +driver.ExecuteScript("mobile: tap", coords); +``` diff --git a/docs/hybrid.md b/docs/hybrid.md new file mode 100644 index 00000000000..6a2e7a76ed3 --- /dev/null +++ b/docs/hybrid.md @@ -0,0 +1,40 @@ +Automating hybrid apps +====================== + +One of the core principles of Appium is that you shouldn't have to change your app to test it. In line with that methodology, it is possible to test hybrid web apps (e.g., the "UIWebView" elements in an iOS app) the same* way you can with Selenium for web apps. There is a bit of technical complexity required so that Appium knows whether you want to automate the native aspects of the app or the web views, but thankfully, we can stay within the WebDriver protocol for everything. + +Here are the steps required to talk to a web view in your Appium test: + +1. Navigate to a portion of your app where a web view is active +1. Call [GET session/:sessionId/window_handles](http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/window_handles) +1. This returns a list of web view ids we can access +1. Call [POST session/:sessionId/window](http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/window) with the id of the web view you want to access +1. (This puts your Appium session into a mode where all commands are interpreted as being intended for automating the web view, rather than the native portion of the app. For example, if you run getElementByTagName, it will operate on the DOM of the web view, rather than return UIAElements. Of course, certain WebDriver methods only make sense in one context or another, so in the wrong context you will receive an error message). +1. To stop automating in the web view context and go back to automating the native portion of the app, simply call `"mobile: leaveWebView"` with execute_script to leave the web frame. + +## Wd.js Code example + +```js + // assuming we have an initialized `driver` object working on the UICatalog app + driver.elementByName('Web, Use of UIWebView', function(err, el) { // find button to nav to view + el.click(function(err) { // nav to UIWebView + driver.windowHandles(function(err, handles) { // get list of available views + driver.window(handles[0], function(err) { // choose the only available view + driver.elementsByCss('.some-class', function(err, els) { // get webpage elements by css + els.length.should.be.above(0); // there should be some! + els[0].text(function(elText) { // get text of the first element + elText.should.eql("My very own text"); // it should be extremely personal and awesome + driver.execute("mobile: leaveWebView", function(err) { // leave webview context + // do more native stuff here if we want + driver.quit(); // stop webdrivage + }); + }); + }); + }); + }); + }); + }); +``` + +* For the full context, see [this node example](https://github.com/appium/appium/blob/master/sample-code/examples/node/hybrid.js) +* *we're working on filling out the methods available in web view contexts. [Join us in our quest!](http://appium.io/get-involved.html) diff --git a/docs/ios-deploy.md b/docs/ios-deploy.md new file mode 100644 index 00000000000..f55e0cf6e06 --- /dev/null +++ b/docs/ios-deploy.md @@ -0,0 +1,64 @@ +Deploying your iOS app to your device +===================================== +To prepare for your Appium tests to run on a real device, you will need to: + +1. Build your app with specific device-targeted parameters +1. Use [fruitstrap](https://github.com/ghughes/fruitstrap), a 3rd-party tool, to deploy this build to your device + +## Xcodebuild with parameters: +A newer xcodebuild now allows settings to be specified. Taken from [developer.apple.com](https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man1/xcodebuild.1.html): + +``` +xcodebuild [-project projectname] [-target targetname ...] + [-configuration configurationname] [-sdk [sdkfullpath | sdkname]] + [buildaction ...] [setting=value ...] [-userdefault=value ...] +``` + +This is a resource to explore the available [settings](https://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-DontLinkElementID_10) + +``` +CODE_SIGN_IDENTITY (Code Signing Identity) + Description: Identifier. Specifies the name of a code signing identity. + Example value: iPhone Developer +``` + +PROVISIONING_PROFILE is missing from the index of available commands, but may be necessary. + +Specify "CODE_SIGN_IDENTITY" & "PROVISIONING_PROFILE" settings in the xcodebuild command: + +``` +xcodebuild -sdk -target -configuration CODE_SIGN_IDENTITY="iPhone Developer: Mister Smith" PROVISIONING_PROFILE="XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX" +``` +On success, the app will be built to your ```/build/-iphoneos/.app``` + +## Deploy using Fruitstrap +Go clone a forked version of fruitstrap as the [ghughes version](https://github.com/ghughes/fruitstrap) is no longer maintained. Success has been confirmed with the [unprompted fork](https://github.com/unprompted/fruitstrap), but others are reportedly functional. + +Once cloned, run ``make fruitstrap`` +Now, copy the resulting ``fruitstrap`` executable to your app's project or a parent directory. + +Execute fruitstrap after a clean build by running (commands available depend on your fork of fruitstrap): +``` +./fruitstrap -d -b -i +``` +If you are aiming to use continuous integration in this setup, you may find it useful to want to log the output of fruitstrap to both command line and log, like so: +``` +./fruitstrap -d -b -i 2>&1 | tee fruit.out +``` +Since fruitstrap will need to be killed before the node server can be launched, an option is to scan the output of the fruitstrap launch for some telling sign that the app has completed launching. This may prove useful if you are doing this via a Rakefile and a ``go_device.sh`` script: +``` +bundle exec rake ci:fruit_deploy_app | while read line ; do + echo "$line" | grep "text to identify successful launch" + if [ $? = 0 ] + then + # Actions + echo "App finished launching: $line" + sleep 5 + kill -9 `ps -aef | grep fruitstrap | grep -v grep | awk '{print $2}'` + fi + done +``` +Once fruitstrap is killed, node server can be launched and Appium tests can run! + +Next: +[Running Appium on Real Devices](https://github.com/appium/appium/wiki/Running-Appium-on-Real-Devices) diff --git a/docs/real-devices.md b/docs/real-devices.md new file mode 100644 index 00000000000..858056e13df --- /dev/null +++ b/docs/real-devices.md @@ -0,0 +1,28 @@ +Appium on real devices +====================== +Appium has preliminary support for real device testing. + +To get started on a real device, you will need the following: + +1. An Apple Developer ID and a valid Developer Account with a configured distribution certificate and provisioning profile. +2. An iPad or iPhone. +3. The source code of your app. +4. A Mac with XCode and the XCode Command Line Developer Tools + +Provisioning Profile +--- + +A valid iOS Development Distribution Certificate and Provisioning Profile are necessary to test on a real device. You can find information about configuring these in the [Apple documentation](http://developer.apple.com/library/ios/#documentation/ToolsLanguages/Conceptual/YourFirstAppStoreSubmission/TestYourApponManyDevicesandiOSVersions/TestYourApponManyDevicesandiOSVersions.html) + +You will also need to [sign your app](http://developer.apple.com/library/ios/#documentation/ToolsLanguages/Conceptual/YourFirstAppStoreSubmission/ProvisionYourDevicesforDevelopment/ProvisionYourDevicesforDevelopment.html#//apple_ref/doc/uid/TP40011375-CH4-SW1). + +Running your tests with Appium +--- + +Once your device and app are configured, you can run tests on that device by passing the -U flag to server.js: + +``` +node server.js -U --app +``` + +This will start Appium and have Appium use the device to test the app. diff --git a/docs/style-guide.md b/docs/style-guide.md new file mode 100644 index 00000000000..e27524ba5f0 --- /dev/null +++ b/docs/style-guide.md @@ -0,0 +1,102 @@ +Style guide for contributors +============================ + +Thanks for your contribution to Appium! Here are the principles we use when writing javascript. Please conform to these so we can merge your pull request without going back and forth about style. The main principle is: *make your code look like the surrounding code*. + +Linting +------- +All code (except for code in `bootstrap.js` which uses proprietary Apple methods) must pass JSLint. To check your code, you can simply run `grunt lint` from the Appium repo dir. If you've created a new .js file, please make sure it is covered by the wildcards in `grunt.js` or that it is added specifically. + +It's easy to have your code linted as you type, which makes the whole process much smoother. We like [jshint](http://www.jshint.com), which has integrations with a lot of source code editors. The file `.jshintrc` is checked into the repo, and its contents are: + +```json +{ + "laxcomma": true, + "strict": true, + "undef": true, + "unused": true, + "trailing": true, + "node": true, + "es5": true +} +``` + +These defined what we want to see warnings about, etc..., while we're editing. See [this page](http://www.jshint.com/platforms/) for the list of editors and platforms and how to get your editor set up with automatic linting. + +Style notes +------ +* Use two spaces for indentation, *no tabs* +* Use single spaces around operators + ```js + var x = 1; + ``` + not + ```js + var x=1; + ``` +* Spaces after commas and colons in lists, objects, function calls, etc... + ```js + var x = myFunc("lol", {foo: bar, baz: boo}); + ``` + not + ```js + var x = myFunc("lol",{foo:bar,baz:boo}); + ``` +* Always end statements with semicolons +* Comma-first + ```js + var x = { + foo: 'bar' + , baz: 'boo' + , wuz: 'foz' + }; + ``` +* Brackets for `function`, `if`, etc... go on same line, `else` gets sandwiched + ```js + if (foo === bar) { + // do something + } else { + // do something else + } + ``` +* Space after `if`: + ```js + if (foo === bar) { + ``` + not + ```js + if(foo === bar) { + ``` +* Avoid bracketless `if` for one-liners: + ```js + if (foo === bar) { + foo++; + } + ``` + not + ```js + if (foo === bar) + foo++; + ``` +* Use `===`, not `==`, and `!==`, not `!=` for no surprises +* Line length shouldn't be longer than 79 characters +* Break up long strings like this: + ```js + myFunc("This is a really long string that's longer " + + "than 79 characters so I broke it up, woo"); + ``` +* Comments should line up with code + ```js + if (foo === 5) { + myFunc(foo); + // foo++; + } + ``` + not + ```js + if (foo === 5) { + myFunc(foo); + //foo++; + } + ``` +* More to come....