Joustie's blog

Mar 29, 2016 - 5 minute read - ci nodejs react javascript agile jenkins cordova mocha saucelabs appium

Part 3: Running CI on Sauce Labs

I have started a small github project for hosting a demo test.

In this project there is an Appium test for the TodoMVC app we built in earlier posts and this test can be run on Sauce Labs with accompanying scripts. First thing is to get a trial account at Sauce Labs.

BTW, Sauce Labs, Inc. is at Microsoft’s Build conference right now and they have announced better support for Visual Studio (with a plugin) and the fact that Microsoft itself is going to support the same automation API (the JSON Wire Protocol).

Anyway, back to the example. Let’s start by cloning the example test. Let’s pretend you build the app from the earlier posts.

Clone the TodoMVC example test


cd $HOME/tmp/blogpost

git clone https://github.com/Joustie/appium-tests.git

cd appium-tests

#The tests use mocha and WD such, so they need to be installed

npm install

Upload the app package to Sauce Labs

So now we are ready to upload the earlier created iOS build to Sauce Labs, in what they call ‘sauce-storage’. It’s storage associated with your Sauce Labs account, nobody but you can reference and use it and it’s temporary.

Open the file upload_todo.sh to configure the script with your settings.

#change these variables
SAUCE_USERNAME=set this to yours
SAUCE_ACCESS_KEY=set this to yours
#make sure the files are zipped (ususally just zip the .app dir on the mac)
REMOTE_FILE=the referenced name in sauce storage
LOCAL_FILE=the local filename you wish to upload (I would use the absolute filename, but hey, that's just me)

The remote file in my case would be called ‘todo.app.zip’ and the local file $HOME/tmp/blogpost/todomvcios/platforms/ios/build/emulator/TodoMVCiOS.zip This zip needs to be created by:

cd $HOME/tmp/blogpost/todomvcios/platforms/ios/build/emulator
zip -r TodoMVCiOS.zip TodoMVCiOS.app

To be clear , TodoMVCiOS.app is the app file created by the build in our earlier post.

Create the Appium test (with Mocha)

Just like the nodejs examples for appium there is a bit of boilerplate code involved to make the test work. You will find a ‘helpers’ directory with functions providing support.

├── LICENSE
├── README.md
├── helpers
│   ├── appium-servers.js   --> credentials and such for appium
│   ├── apps.js  --> the apps to test are defined in here
│   ├── caps.js  --> defines the capabilities for a testrun e.g. the ios version, which device
│   ├── logging.js  --> some logging functionality
│   └── setup.js  --> some basic testing setup
├── ios-todo-on-sauce-9.2.sh  -->shell script to run the test on Sauce Labs
├── ios-todo.js   --> the main test file
├── package.json  --> the package specification with the basic requirements and config info
└── upload_todo.sh  --> this shell scripts uses curl to upload an app package to Sauce Labs storage
```
An important aspect of the test is finding UI-elements in the app. There are several strategies for that in Appium. In this case I used the Xpath-locater strategy. You can find elements by searching for them in the Appium inspector after starting Appium.
![Appium start](/images/2016/03/appium_startup.png)

After starting Appium you can start the inspector. You can find the xpath of elements in the middle column.

![Appium inspector](/images/2016/03/appium_inspector.gif)

You can export the list of elements using the 'copy xml' button for later use.

When you have identified all elements for your test you can write some javascript.

So the main test file is ios-todo.js. You will find some setup code in there, but the main tests begin halfway and are written in Mocha style.

```language-javascript

 it("click to make it editable and fill in value", function () {
    return driver
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]").click()
        .elementByXPath("//UIAApplication[1]/UIAWindow[4]/UIAKeyboard[1]")
        .should.eventually.exist
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]").sendKeys("Test 1 \n")
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAStaticText[3]")
        .should.eventually.exist
      });

  it("enter another value", function () {
    return driver
        .elementByXPath("//UIAApplication[1]/UIAWindow[4]/UIAKeyboard[1]")
        .should.eventually.exist
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]").sendKeys("Test 2 \n")
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAStaticText[4]")
        .should.eventually.exist
      });

  it("select first value, mark complete", function () {
    return driver
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIASwitch[3]")
        .should.eventually.exist
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIASwitch[3]").click()
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]")
        .should.eventually.exist
      });

  it("delete first value", function () {
    return driver
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]").click()
      });

  it("delete second value", function () {
    return driver
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]").click()
        .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]")
        .then(function() {
                console.log("found the button");
                throw Error('Delete button is still there, where it should not be!');
        },function rejectedPromise(){

                //throw Error('Delete button for entry is not there!');
        })
      });

```




### Run the test on Sauce Labs
The main script can be run as:

````language-bash

./ios-todo-on-sauce-9.2.sh

  ios simple
 > CALL init({"browserName":"","appium-version":"1.5","platformName":"iOS","platformVersion":"9.2","deviceName":"iPad Air","app":"sauce-storage:todo.app.zip","name":"ios - todo","tags":["appium,test"]})
 > POST /session {"desiredCapabilities":{"browserName":"","appium-version":"1.5","platformName":"iOS","platformVersion":"9.2","deviceName":"iPad Air","app":"sauce-storage:todo.app.zip","name":"ios - todo","tags":["appium,test"]}}

Driving the web on session: 915c4300e9bf4f9b9f30c0b201c84711

 > RESPONSE init({"browserName":"","appium-version":"1.5","platformName":"iOS","platformVersion":"9.2","deviceName":"iPad Air","app":"sauce-storage:todo.app.zip","name":"ios - todo","tags":["appium,test"]}) "915c4300e9bf4f9b9f30c0b201c84711",null
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]") {"ELEMENT":"0"}
 > CALL element.click()
 > POST /session/:sessionID/element/0/click
 > RESPONSE element.click()
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[4]/UIAKeyboard[1]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[4]/UIAKeyboard[1]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[4]/UIAKeyboard[1]") {"ELEMENT":"1"}
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]") {"ELEMENT":"2"}
 > CALL element.sendKeys("Test 1 \n")
 > POST /session/:sessionID/element/2/value {"value":["Test 1 \n"]}
 > RESPONSE element.sendKeys("Test 1 \n")
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAStaticText[3]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAStaticText[3]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAStaticText[3]") {"ELEMENT":"3"}
    ✓ click to make it editable and fill in value (20955ms)
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[4]/UIAKeyboard[1]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[4]/UIAKeyboard[1]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[4]/UIAKeyboard[1]") {"ELEMENT":"4"}
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]") {"ELEMENT":"5"}
 > CALL element.sendKeys("Test 2 \n")
 > POST /session/:sessionID/element/5/value {"value":["Test 2 \n"]}
 > RESPONSE element.sendKeys("Test 2 \n")
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAStaticText[4]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAStaticText[4]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAStaticText[4]") {"ELEMENT":"6"}
    ✓ enter another value (20192ms)
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIASwitch[3]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIASwitch[3]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIASwitch[3]") {"ELEMENT":"7"}
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIASwitch[3]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIASwitch[3]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIASwitch[3]") {"ELEMENT":"8"}
 > CALL element.click()
 > POST /session/:sessionID/element/8/click
 > RESPONSE element.click()
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]") {"ELEMENT":"9"}
    ✓ select first value, mark complete (5070ms)
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]"}
 > RESPONSE elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]") {"ELEMENT":"10"}
 > CALL element.click()
 > POST /session/:sessionID/element/10/click
 > RESPONSE element.click()
    ✓ delete first value (2545ms)
 > CALL elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]")
 > POST /session/:sessionID/element {"using":"xpath","value":"//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]"}
    ✓ delete second value (1157ms)
 > CALL quit()
 > DELETE /session/:sessionID

Ending your web drivage..

 > RESPONSE quit()
 > CALL sauceJobStatus(true)
 > POST /rest/v1/:user/jobs/:sessionID {"passed":true}
 > RESPONSE sauceJobStatus(true)


  5 passing (3m)
```

The above means all of your tests went ok. If you look closely in the output you'll see the description of the tests from the mocha test (e.g. ✓ delete second value )

You can view the results in your personal dashboard on Saucelabs.com

![Dashboard](/images/2016/03/saucelabsdashboard.png)

There is a video and screenshots of the session if you click on the test.

![Todomovie](/images/2016/03/saucelabsdashboardmovie.png)