Fiddling with Appium Desktop – initial learning and gotchas

What is Appium Desktop ?

Launched about an year ago, Appium desktop is a desktop app that provides a GUI to run the Appium server and (much more importantly) offers a GUI capability to inspect apps under test (locally and remotely)

Through this post I will share my experience with

  • Appium desktop installation and launch
  • Connecting a physical device with Appium desktop and inspecting the app under test

Appium Desktop install process ::

  1. Install the Android SDK as part of the Android Studio IDE ( https://developer.android.com/studio/index.html) I chose that option because I wan to play with Android studio as well. You dont have to , so for Appium’s purposes you just need the Android SDK
  2. Get the latest release for the Appium Desktop installer here –> https://github.com/appium/appium-desktop/releases/tag/v1.5.0
  3. Download, install and click the shortcut to launch Appium Desktop, you should get a screen similar to this

Starting Appium server

  1. Click on the “Start Server..” button above ( I kept the Host and Port default)
  2. And see you should the console screen below

Note a wee gotcha here, that the app “transforms”into the console window here ( in case you are looking for the app and console to exist as two windows, as i was 🙂 )

Connecting to your device

There are two aspects to this –

a) Configuring and connecting your physical device

b) Configuring Appium Desktop ( primarily through setting up capabilities , some pre-reading for that here –> https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md )

For configuring my device, I had mainly set up USB debugging ony my Android device under settings ( I learnt quite a it from this beginner Android Studio tutorial here  )

Next step is now to get Appium Desktop to talk to your device and this process had a few gotchas that I have shared solutions for below

  1. Start a session in Appium Desktop , from the console window above click “File”–> “New session”
  2. The new session part of Appium Desktop should be displayed below

For this post I am only going to focus on the “automatic server”option .

To be able to connect to the device, I had to set some capabilities but had no idea what was the exhaustive list of capabilities to be set.

So, I started out with a minimal list of

{
  "platformName": "Android",
  "platformVersion": "7.0",
  "deviceName": "HUAWEI EVA-L09"
}

See the screenshot below on how to set them up in Appium Desktop 

Note –   You have to find your Android version and phone name . Some basic googling  should be solve that for you

With those capabilities set and the device conected, I clicked Start session to see what happens

Gotcha :: An error popped up re: ANDROID_HOME not being set  – 

 

 

Ok, weird I was expecting this to be set up as part of installing the Android SDK as part of Android Studio IDE, but no, I am too demanding 🙂

To fix this –

  1. Find where your Android SDK directory is ( Googling told me that it is usually @ C:\Users\<user-name>\AppData\Local\Android\Sdk )
  2. Add that to that value to a new environment variable ANDROID_HOME

 

Lets try again ….

Gotchas :: Some more capabilities need to be set ! 😦 

Ok, part of it I can understand i.e. I had completed missed setting a target app that needs to be controlled through Appium

and part that I have no idea about i.e. “app package”

Solving :: installing a target app and setting that capability

  1. Install and download the apk for the app you want tested through Appium. I used this app from the official Appium tutorials page — https://github.com/appium/sample-code/tree/master/sample-code/apps/ApiDemos/bin
  2. For finding the app pacakage capability, I did some further googling and stumbled upon this very useful tutorial — https://www.youtube.com/watch?v=RDjNPnuyA00  .
  3. I used method 3 from the above tutorial ( as I was not using CLI for the purposes of this learning exercise). Important to note that the tutorial advices ( contrary to the error message ) that I would need both the appPackage and appActvity parameters for this to work

Based on the three steps above I set this following capability in Appium desktop

So, now the capabilities JSOn snippet looked like this

{
  "platformName": "Android",
  "platformVersion": "7.0",
  "deviceName": "HUAWEI EVA-L09",
  "appPackage": "io.appium.android.apis",
  "appActivity": ".ApiDemos"
}

Click Start session one more time and see if works ?

Yooooooodley...... no error messages this time and now I can see a new screen
 on Appium Desktop that shows a mirror of my phone with the app on it and 
information on the app's elements etc 


Now , I am all set to inspect and interact with the app under test using 
Appium Desktop and writing tests in Python ! :)

 

#100daysofcode :: Python 3.0 , interrupting an endless loop from the keyboard,gracefully

Recently I started out on a mission to further hone my programming skills. Out of the courses that I explored online, the #100daysofcode course from the TalkPython folks appealed to me the most. It is challenging,structured and the instructors are great.

I will be blogging my micro-learnings onthis journey. This is the first one from Day #3

I was in a situation where I had to execute a piece of code and output it’s  result on the console until the flow is interrupted by the user (through the console)

Ctrl+C can achieve it .However the interruption was not graceful, it resulted in my Python program “crashing”. So, I had handle this through exception handling.

Here the code that I wrote to handle the keyboard interrupt gracefully


try:
start_time = datetime.now()
#do something endlessly
while True:
print(datetime.now())
time.sleep(1)
# until interrupted by the keyboard
except KeyboardInterrupt:
#behave gracefully in case of interruption
time_lapsed = (datetime.now() start_time)
print('Time elapsed (hh:mm:ss.ms) {}'.format(time_lapsed))

view raw

interrupt.py

hosted with ❤ by GitHub


 

Creating and preserving Cookies with Cypress.io

Continuing my joy ride with Cypress.io , I recently wrote a test to check for a successful logon for a public retail website.

While the test was easy enough to write but the logon was failing (inspite of the correct credentials) with the below error message returned from the website

Debugging the failure 

  1. Double check the credentials ? Done , nothing wrong there
  2. Compare against a working scenario – AHA !

I opened up Chrome Dev tools and diffed a working network request versus what Cypress was sending.  A blatant , clear to the naked eye, difference was the number of cookies being set between the Cypress call and manual browser call.

Cookies in Cypress call –

Cookies in a human generated browser call – 

Ok, so lets make these cookies consistent and see if the login process works through Cypress

Getting the cookie list and values

Within Chrome’s settings pages there is an option to see all the list and values of all the cookies that are present in the browser

I cherry picked what I could not see being set by Cypress and then the challenge moved to , how to set them up in the Cypress test ?

How do we set cookies in a Cypress test ?

A quick search in the Cypress API revealed the setCookie() method

Viola ! Now, I just to add those cookies into my test and see if this works ?

So, I added this bit of code to my test

Rerunning the test with the missing cookies set 

Brilliant…. the test and all it’s assertions pass now and in dev tools I can see that the cookies are not being set 🙂

Exploring the Cypress API – getting and asserting on a DOM element’s children

A common operation during Web UI tests is to view and validate contents of a drop down.

As part of my exploration of the ever likable Cypress automation toolset, I recently wrote a UI workflow tests that amongst other things validated the contents and count of a drop down list’s elements.

I was dreading that I would have to figure out a way using JavaScript/JQuery to select the drop down and iterate my way through the drop down’s children and perform an assertion on the item and count  the children.

But, behold.., the fact fact that the Cypress API has handy dandy method to get all the children of a DOM element

Using .children() and the readable Chai assertions made this a joy


it('verify the total count and existence of a particular element in the dropdown',() => {
// get the element and it's associated children
cy.get('#sort-by-menu-header > div > ul').children()
// perform the assertion on existence of a particular item in the drop down
.should('contain','New Arrivals')
// perform the assertion on the number of children i.e. options in the drop down
.and('have.length','6')
})

Bonus tip-

Note the clever use of “and” in adding multiple assertions on the element , avoiding the use of multiple “shoulds” and making the code human readable.

A newbie’s toe dip into JS based Test automation using Cypress.io

Recently, I have started exploring alternatives to Selenium Webdriver for E2E Web UI automation and the first tool set that I tried my hand at was Cypress

Cypress is a compellingly marketed offering with a touted usp of –

  • Not being a framework based off Selenium
  • Leveraging off existing JavaScript BDD ecosystem ( Mocha and Chai)
  • Promising out of the box reporting,debugging and test recording functionality

Details here –>  https://www.cypress.io/features/

Here is my initial experience report,after spending couple of hours with Cypress .

  1. Got started by viewing Brian Mann’s (creator of Cypress) introductory talk on Cypress and heard out the problems he was trying to solve through Cypress and the architectural decisions that he took and why he took?
  2. Next up I started exploring the documentation – https://docs.cypress.io/guides/overview/why-cypress.html#
  3. Then tried installing Cypress through it’s NPM package but did not have smooth time with the installation failing to initialize the tool and about 30 minutes later it turned out to be a show stopping recently reported issue 
  4. Took the alternate route of their direct download non-NPM based installer and finally after an hour of jiggery-pokery was able to get Cypress up and running

Things were smoother (and very impressive) from here on though

5. The API documentation is informative and using their  tutorial,within 15 minutes I had written a simple BDD-esque test that ….

a) Opened Amazon.com’s sign on page(by querying and clicking an element from the homepage) and assert on the text in the title of the web page

b) Provided me live debugging(including page events) and snap shot capability

c) Provided me post test execution capability to travel back in time to view those snap shots and debug the visual state of the browser at various steps in the test

d)Provided me nicely formatted side-by-side visual logging of my assertion ,test results and page events (including stuff like XHR requests)

All this just by writing the below lines of code ( and without having to worry about including explicit waits 😉 )


describe('First_Cypress_Test',function() {
it("Verifies that the correct title of the Amazon.com's sign in page is displayed",function() {
cy.visit('https://amazon.com&#39;)
cy.get("#nav-link-accountList > span.nav-line-2").click()
cy.title().should('eq','Amazon Sign In1')
})
})

view raw

cypress.js

hosted with ❤ by GitHub

Although there are some sobering aspects to Cypress in terms of support only for Chrome and no Appium alternative, but from an initial peak this looks like a very impressive ( and powerful) alternative to Selenium focusing on stability,Tester/Developer efficiency and mature Test/SUT debugging capabilities

With my toe dipping experience being positive, I am firmly motivated now to take a deeper dive into Cypress.io !

 

 

Python 3.0 learnings – List comprehensions

One of the strengths of a simple (yet powerful) language like Python is to achieve the same with less lines of code.

A “Pythonic” concept that proves this is list comprehensions

Simply put, it enables you concisely create a list in a single line of code based on some logic presented as an iterable object

Syntax is –

<to_be_create_list_name> = [i for i in <logic to create an iterable object(s)>]

And they powerful thing is that conditionals can be used in the above syntax of create an iterable object based on some conditional logic

Example –

You are given a list that has blank space characters in it, find out at which positions in the list do those blank space chars exist ? and create a new list with to hold those positions ( to be used downstream for a business rule)

Solution # 1 –

My original bloatey way to achieve it was


list_2 = [] #create a blank to be populated list
for x in range(len(list_1)): # loop through the lenght of the original list
if list_1[x] == ' ': # if the value at that particular index is a blank space
list_2.append(x) # add the index to the new list
print(list_2)

Solution # 2 –

A much more concise way discovered (through googling) that uses list comprehension  😉


list_2 = [x for x in range(len(list_1)) if list_1[x] == ' ']

view raw

List_comp.py

hosted with ❤ by GitHub

Note – how the condition to check for blank chars and the loop to iterate through the link of the list, have been combined into a single line

Python 3.0, you beauty !

 

 

 

Using cURL commands in a REST client , without wanting to know cURL ;)

I am a newbie to cURL and dont understand cURL commands , neither have I used cURL to make REST API calls before.

Recently I encountered a situation where I had access to some cURL “code” and I had to use that perform some GET and POST operations on a REST end-point.

Due to lack of time ( and laziness) to learn cURL , I took a punt and started exploring as to whether the marvelous Postman tool had an easy way to convert cURL commands to some kind of REST API call structure .

And viola , Postman did not disappoint again , this can be achieved in a few clicks

Step 1 :: Find cURL commands that represent your code –

curl https://{subdomain}.{domain}.com/api/v2/groups.json \
  -H "Content-Type: application/json" -d '{"group": {"name": "My Group"}}'
  -v -u {email_address}:{password} -X POST

Step 2: Open Postman and click import (next to the “new” button)

Step 3: In the ensuing dialog box, choose the raw text tab and paste the cURL snippet

Step 4: Click import and there you have it , Postman has created a POST request with the desired end point & JSON payload from the cURL commands in a new tab . You can now tweak away and modify/run the call as you see fit 🙂

 

 

Python & Selenium Webdriver :: working with dropdown options inside a select tag

During my Python & Selenium self-learning I came across a blocker while trying to access and select elements of a drop down.

Problem statement – 

As a traveler, I want to select Business class for my flight on the Air NewZealand website , just to remind myself how atrociously expensive flying business class is .

Initial analysis – 

Using Chrome Dev tools, I inspected the drop down and was expecting the various drop down entries to each have a unique id/index , so that I can select “business class” directly.

But lo-behold, the drop-down element is a <select> HTML that I had not worked with before. And each option was a <option> tag nested under the <select> tag.

Approach – 

Potential ways I thought to solve this –

  1. Find the drop down using it’s id ( driver.find_element_by_id  ) and find a way to iterate through the options based on their text . Likely to be brittle if the option text changes
  2. Directly find the XPath of the option that I want and click it .

On further googling however, I found that the Selenium library offered an out of the box class to handle <select> web elements ! 🙂

https://seleniumhq.github.io/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.select.html

This class has some handy dandy methods to retrieve all the options under the select tag , select them by value or index etc.

options
Returns a list of all options belonging to this select tag
select_by_index(index)
Select the option at the given index. This is done by examing the “index” attribute of an element, and not merely by counting.

Args :
  • index – The option at this index will be selected

throws NoSuchElementException If there is no option with specisied index in SELECT

select_by_value(value)
Select all options that have a value matching the argument. That is, when given “foo” this would select an option like:

<option value=”foo”>Bar</option>

Args :
  • value – The value to match against

throws NoSuchElementException If there is no option with specisied value in SELECT

My solution – 

Here is a simple Python script the uses the Select class and some of it’s method to solve the problem above.


from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import TimeoutException
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select
# for easy future changes and code maintainbility i have added all the locators and parameters like username/password at the top of the test
driver = webdriver.Chrome()
CONTINUE_BUTTON = (By.XPATH,'//*[@id="cms-search-panel-container"]/div/div/div/form/div[3]/div/button')
FLIGHT_CLASS_DROPDOWN = (By.ID,'serviceclass')
FLIGHT_CLASS_NAME = 'BUSINESS'
URL = 'https://www.airnewzealand.co.nz/home&#39;
class AirNZ_flight_class:
def verify_flight_class_dropdown_value(self):
driver.maximize_window()
driver.get(URL)
# wait for the continue button to appear
continue_button = WebDriverWait(driver,10).until(EC.presence_of_element_located(CONTINUE_BUTTON))
continue_button.click()
# wait for the fligh class dropdown to appear
flight_class_dropdown = WebDriverWait(driver,10).until(EC.presence_of_element_located(FLIGHT_CLASS_DROPDOWN))
#select the drop down using the Select class imported from from selenium.webdriver.support.select
select = Select(flight_class_dropdown)
try:
# select an option from the drop down based on a value, i did not go for a index in case the index changes due to addition or removal of entries
select.select_by_value(FLIGHT_CLASS_NAME)
except(NoSuchElementException,TimeoutException) as e:
# fail the Test if the element can not be found or timeout occurs
print('Test failed, the flight class drop down could not be found ')
finally:
driver.quit()
test = AirNZ_flight_class()
test.verify_flight_class_dropdown_value()

view raw

selenium.py

hosted with ❤ by GitHub