Wednesday 30 June 2021

Python Appium - Difference between Implicit wait and Explicit wait

 Why we need to use wait?

We need to use the wait mechanism because the webdriver is quite faster than the Page load. Sometime it reaches to page and on webpage few of the elements are not loaded fully. In short DOM takes time to load all the elements on screen and this may lead webdriver does not found the element which will raise ElementNotVisible exception.

The main idea behind using waits in our script is to keep webdriver and page in sync by providing some delays between element identification and action performed.

There are two types of waits that are available with webdriver object

            1. Implicit wait

            2. Explicit wait

Let us see above two waits with real time examples

1. Implicit wait:

Implicit wait tells webdriver object to wait for certain amount of time i.e. whatever we have mentioned in the method. The implicit wait once set, it would be set for the entire life of driver object for that particular session. For example we have defined a implicit wait for 5 secs and DOM takes to load the element for 6 secs then it would raise an exception as ElementNotVisible. Suppose the DOM took 3 secs to load the element then implicit wait would be only 3 secs and would continue with further execution. This is the best way to reduce the time to identify the element on page. The custom method time.sleep(5) would wait of irrespective of whether element is found or not. This may result in slow execution. So its always preferable to use implicit wait rather than using time.sleep() method. There are some cons using implicit wait in your code,

1. The performance issues may be escaped and would never be catch using your script

2. The execution becomes little slower as implicit wait is introduced.

def test_calculator_click_number():
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel API 24',
'platformVersion': '7.0',
'appPackage': 'com.android.calculator2',
'appActivity': 'com.android.calculator2.Calculator',
'systemPort': 8201
}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
driver.implicitly_wait(5)
log = BaseClass().get_logger()
log.info("-------------- Click number on calculator app --------------")
cal = Calculator(driver)
cal.click_number(9)
log.info("-------------- Click number on calculator app test is completed --------------")

So in above example, driver object would wait for 5 or less than 5 second  to identify the element on page. This would be happening for each statement it goes through the script.

2. Explicit wait:

Once defined explicit wait, the wait is applied only for specific element which takes longer time to load.

Explicit wait is also similar to implicit wait, for example if wait is defined as 5 secs and element is found in 3 seconds then would continue next line of code. This does not make your execution slower which is found in implicit wait. Explicit wait is little complicated to implement than implicit wait because here we have to find the element first on which explicit wait has to be applied and later have to add in the script.

def __init__(self, driver):
self.driver = driver
self.formula = WebDriverWait(self.driver, 5).until(
e.visibility_of_element_located((
MobileBy.ID, "com.android.calculator2:id/formula")))
self.result = WebDriverWait(self.driver, 5).until(
e.visibility_of_element_located((
MobileBy.ID, "com.android.calculator2:id/result")))
self.divide_button = WebDriverWait(self.driver, 5).until(
e.visibility_of_element_located((
MobileBy.ID, "com.android.calculator2:id/op_div")))
self.multiply_button = WebDriverWait(self.driver, 5).until(
e.visibility_of_element_located((
MobileBy.ID, "com.android.calculator2:id/op_mul")))
self.minus_button = WebDriverWait(self.driver, 5).until(
e.visibility_of_element_located((
MobileBy.ID, "com.android.calculator2:id/op_sub")))
self.plus_button = WebDriverWait(self.driver, 5).until(
e.visibility_of_element_located((
MobileBy.ID, "com.android.calculator2:id/op_add")))
self.equals_button = WebDriverWait(self.driver, 5).until(
e.visibility_of_element_located((
MobileBy.ID, "com.android.calculator2:id/eq")))
self.delete_button = WebDriverWait(self.driver, 5).until(
e.visibility_of_element_located((
MobileBy.ID, "com.android.calculator2:id/del")))

Tuesday 22 June 2021

Mobile Application Testing: Python Appium - How to automate gestures - Tap, lo...

Mobile Application Testing: Python Appium - How to automate gestures - Tap, lo...: So above gestures are the part of TouchAction class of appium. So let us go in details of this class TouchActions: TouchAction objects conta...

Tuesday 1 June 2021

Python Appium - Locating elements using xpath,class,accessibility ID,ID and Image

 Appium Locators

There are five locators which are extensively used for identifying mobile element using appium inspector. So let us see each with examples.

You can also check

ID

This can be used when you know the id attribute of an element. If the element is not found by mentioned attribute, it would raise an exception "NoSuchElementException"

So let us take example of dialer page. Here we have launched dialer screen using below desired capabilities
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel 1 API 28',
'platformVersion': '9.0',
'appPackage': 'com.google.android.dialer',
'appActivity': 'com.google.android.dialer.extensions.GoogleDialtactsActivity'

}
In above yellow marked item was identified by any of the locators so I have decided to go by co ordinate method. Third option given appium inspector is 'Tap By Co-ordinates'. For co ordinates, first click on recording and click on the dialpad item. you would see auto generated code for it as below
You can include above code in your automation script as below
from appium.webdriver.common.touch_action import TouchAction
'''Tap on dialer pad, as we could not see any valid element identifiers'''
touch = TouchAction(driver)
touch.tap(x=955, y=1515).perform()
So I have selected python language here, you can select any other language in which you are writing your automation scripts. Copy paste python code in your script. On this page let us click on dial for id attribute
Let us check whether we have found a unique identifier. On appium inspector there is an option on top beside recording option called 'Search for Element'.


So once it is validated here, you can include it in your code as below
driver.find_element_by_id("com.google.android.dialer:id/digits")
You need to type some number on the element you have identified for this you can use send_keys() method.
driver.find_element_by_id("com.google.android.dialer:id/digits").send_keys("810000000")
Complete code would look like as below
import pytest
from appium import webdriver
from time import sleep
from appium.webdriver.common.touch_action import TouchAction

def __init__(self, driver):
self.driver = driver

def test_locators():
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel 1 API 28',
'platformVersion': '9.0',
'appPackage': 'com.google.android.dialer',
'appActivity': 'com.google.android.dialer.extensions.GoogleDialtactsActivity'

}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)

'''Locate Element by ID'''
'''Tap on dialer pad, as we could not see any valid element identifiers'''
touch = TouchAction(driver)
touch.tap(x=955, y=1515).perform()
driver.find_element_by_id("com.google.android.dialer:id/digits").send_keys("810000000")
print("\n Element by ID is found")
To execute run below command,
py.test test_locators.py -v -s
Classname
This can be used when you know the classname attribute of an element. If the element is not found by mentioned attribute, it would raise an exception "NoSuchElementException"

Let us take example of calculator native app. The desired capabilities would be like as below
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel 1 API 28',
'platformVersion': '9.0',
'appPackage': 'com.android.calculator2',
'appActivity': 'com.android.calculator2.Calculator'

}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
Code snippet for identify element by class name would be as below
'''Locate Element by Class'''
driver.find_element_by_class_name("android.widget.ImageButton")
print("Class Element is found")


Accessibility ID:
This can be used when you know the accessibility id attribute of an element. If the element is not found by mentioned attribute, it would raise an exception "NoSuchElementException"

Let us take example of flipkart app for this.

The code would look like as below,
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel 1 API 28',
'platformVersion': '9.0',
'noReset': 'true',
'appPackage': 'com.flipkart.android',
'appActivity': 'com.flipkart.android.activity.HomeFragmentHolderActivity'

}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
time.sleep(20)
'''Locate Element by Accessibility ID'''
driver.find_element_by_accessibility_id("Flipkart home")
print("Element found")
Xpath
This can be used when you know the xpath attribute of an element. If the element is not found by mentioned attribute, it would raise an exception "NoSuchElementException"

The code would look like as below,
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel 1 API 28',
'platformVersion': '9.0',
'noReset': 'true',
'appPackage': 'com.flipkart.android',
'appActivity': 'com.flipkart.android.activity.HomeFragmentHolderActivity'

}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
time.sleep(20)
'''Locate Element by Xpath'''
driver.find_element_by_xpath("//android.widget.ImageView[@content-desc='Flipkart home']")
print("Element found by xpath")

Image:
This can be used when you know the image attribute of an element. If the element is not found by mentioned attribute, it would raise an exception "NoSuchElementException"
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel 1 API 28',
'platformVersion': '9.0',
'noReset': 'true',
'appPackage': 'com.flipkart.android',
'appActivity': 'com.flipkart.android.activity.HomeFragmentHolderActivity'

}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
time.sleep(20)
'''Locate Element by Image'''
driver.find_element_by_image('c://sample/2021/to/img.png')
print("Element found by Image")


Monday 31 May 2021

Python Appium - Desired Capabilities - Importance of noReset in Script

 What if you don't include noReset option of desired capabilities?

By default this capability is set to false.

desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel 2 API 28',
'platformVersion': '9.0',
'appPackage': 'com.flipkart.android',
'appActivity': 'com.flipkart.android.activity.HomeFragmentHolderActivity'

}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)

So what exactly it does that if noReset is set to false, each time your test select capabilities, it will clear the cache, data for the application under test.

Lets take an actual test scenario of flipkart app. If user want to land on home screen, they have to first select the language, enter number or skip the page and then user is landed on home screen.

Select Language

Enter Number

On HomeScreen

If noReset : true, It will not clear the cache, data and user is landed on home screen. So selecting language and entering contact number would be a one time activity in this case.

desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel 2 API 28',
'platformVersion': '9.0',
'noReset': 'true',
'appPackage': 'com.flipkart.android',
'appActivity': 'com.flipkart.android.activity.HomeFragmentHolderActivity'

}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
On Home Screen







Python Appium - Multiple scroll


Difference between single scroll and multiple scroll


Let us implement multiple scroll for flipkart app
For multiple scroll you have to use TouchAction class as below.
import pytest
from appium import webdriver
from utilities.BaseClass import BaseClass
from time import sleep
from appium.webdriver.common.touch_action import TouchAction

def __init__(self, driver):
self.driver = driver

def test_flipkart_homescreen():
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5556',
'deviceName': 'Pixel 3 API 24',
'platformVersion': '7.0',
'noReset': 'true',
'appPackage': 'com.flipkart.android',
'appActivity': 'com.flipkart.android.activity.HomeFragmentHolderActivity'

}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)

log = BaseClass().get_logger()
log.info("-------------- Launching Flipkart App --------------")
sleep(5)
log.info("-------------- User is on Flipkart home screen --------------")
touch = TouchAction(driver)
touch.press(x=41, y=1576).move_to(x=44, y=1453).release().perform()

for i in range(4):
touch = TouchAction(driver)
touch.press(x=41, y=1576).move_to(x=44, y=1453).release().perform()
sleep(5)


So the basic difference is multiple scroll can be done multiple times using for loop. such scenarios arises when you want to scroll the items on page till last element is displayed. Sometime "scroll to top" such text is displayed once user reaches to the end of page.



Thursday 27 May 2021

Python Appium - How to handle list of Elements

 How to handle list of Elements

There are situations where all the elements on a particular page have same attribute however we have to extract a unique one and click on it.

So let us take a simple example for this SMS a native app on device


Here I want to click on settings So I need to find an unique locator for the same. So let us see how we can capture the it

For this open new session in appium and click on screenshot


So if you see all the elements have same attribute, its a list which have common attribute and here we have to find a unique one

ist_of_values = driver.find_elements_by_xpath(
"//android.widget.TextView[@resource-id='com.google.android.apps.messaging:id/title']")
expected_list =['Archived','Blocked contacts','Messages for web','Settings','Help&feedback']
actual_list = []

print(len(list_of_values))
for values in list_of_values:
ele = values.get_attribute("text")
actual_list.append(ele)

print(actual_list)

So we can identify elements by xpath. Once the element is identified wee can iterate through list of values. Before that we can get the length of list of values. Later append the text in actual_list[].

Let us see what is the output for this.


So from output we can see that length of list is 5 and the actual list is displayed. So let us get back to the point that we want to click on settings now. How to click on that

#Tap on Settings options
driver.find_element_by_xpath("//android.widget.TextView[@text='Settings']").click()

So you need to pass text= Settings

Complete code as below:

import pytest
from appium import webdriver
from pageObject.message.message_app import Message
from utilities.BaseClass import BaseClass
from time import sleep


def __init__(self, driver):
self.driver = driver


def test_send_message_delivery_on():
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel 2 API 28',
'platformVersion': '9.0',
'appPackage': 'com.google.android.apps.messaging',
'appActivity': 'com.google.android.apps.messaging.ui.ConversationListActivity',
'new/commandTimeout': 600
}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
driver.find_element_by_xpath("//android.widget.ImageView"
"[@content-desc='More options']").click()
sleep(7)
list_of_values = driver.find_elements_by_xpath(
"//android.widget.TextView[@resource-id='com.google.android.apps.messaging:id/title']")
expected_list = ['Archived','Blocked contacts',
'Messages for web','Settings','Help & feedback']
actual_list = []
print("\n ")
print(len(list_of_values))
for values in list_of_values:
ele = values.get_attribute("text")
actual_list.append(ele)

print(actual_list)

assert expected_list == actual_list, "List did not matched..."

#Tap on Settings options
driver.find_element_by_xpath("//android.widget.TextView[@text='Settings']").click()

Here Attaching with video of our scenario.




Monday 24 May 2021

Python Appium - How to automate gestures - Tap, long press and swipes/scroll

So above gestures are the part of TouchAction class of appium. So let us go in details of this class

TouchActions:
TouchAction objects contains chain of actions. TouchAction class have only one method perform and we should always end our touchAction sequence with .perform() method.

MultiTouch:
MultiTouch object are collection of TouchActions. MultiTouch have add() and perform() methods.

So let us see a real time example.

tap(): You need to pass x and y co-ordinates where you want to tap.
You can find the tap co ordinates from appium session. Give the desired capabilities and it will launch the session screen for you.

On top of screen there is an option swipe by co ordinates. Place the mouse over the item for which you want to tap. At top left corner you could see x and y coordinate values.




And we have the code which we implemented for tap and swipe up and down on Settings app
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction
import time

def __init__(self, driver):
self.driver = driver

def test_tap_example():
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel_3_API_24',
'platformVersion': '7.0',
'appPackage': 'com.android.settings',
'appActivity': 'com.android.settings.Settings'
}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)

driver.find_element_by_id("android:id/title").get_attribute("text")

# Tap example
# Tap on Notification option given under settings
user_actions = TouchAction(driver)
user_actions.tap(x=253, y=1107).perform()
time.sleep(5)
driver.back()

#Swipe/Scroll up and down example
time.sleep(5)
user_actions.press(x=434, y=872).move_to(x=437, y=656).release().perform()
time.sleep(5)
user_actions.press(x=468, y=1165).move_to(x=465, y=1401).release().perform()
time.sleep(5)

Let us see the implementation for long press on dialer app



So here you can record the actions for swipe and then the co ordinated appear . You can select the programming language as well whatever is required by you.

from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction
import time

def __init__(self, driver):
self.driver = driver


def test_long_press_example():
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5554',
'deviceName': 'Pixel_3_API_24',
'platformVersion': '7.0',
'appPackage': 'com.android.dialer',
'appActivity': 'com.android.dialer.DialtactsActivity'
}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
# Long press for sending the number
user_actions = TouchAction(driver)
user_actions.tap(x=543, y=1890).perform()
time.sleep(5)

number = driver.find_element_by_id("com.android.dialer:id/digits")
number.send_keys("810000000")
user_actions.long_press(number)
time.sleep(5)


Thursday 20 May 2021

Python Appium - Parallel Test execution on mobile devices

 Why there is requirement for Parallel execution of test script on multiple devices?

Having single script and executing them parallel on multiple devices can actually reduce the efforts, resources and mainly the "TIME".

So let us start with parallel test execution script. first we would need below package. For info refer https://pypi.org/project/pytest-xdist/

pip install pytest-xdist

Once the package is installed, we can make sure that our test will run on multiple devices in parallel

For that I have considered pytest framework. Lets start the code

@pytest.mark.parametrize("udid,platformVersion,deviceName,systemPort,",[('emulator-5554','7.0','Pixel API 24','8201'),('emulator-5556','7.0','Pixel 3 API 24','8202')]) 

Basically you have to use parameterize annotation here to pass multiple device info, which it will pick at the time of creation of thread. Two parameters means two threads will be created for parallel execution.

import pytest
from appium import webdriver
from pageObject.calculator.calculator_app import Calculator
from utilities.BaseClass import BaseClass


def __init__(self, driver):
self.driver = driver


@pytest.mark.parametrize("udid,platformVersion,deviceName,systemPort,",[('emulator-5554',
'7.0','Pixel API 24','8201'),('emulator-5556','7.0','Pixel 3 API 24','8202')])
def test_calculator_add_number(udid, platformVersion, deviceName, systemPort):
desired_caps = {
'platformName': 'android',
'udid': udid,
'deviceName': deviceName,
'platformVersion': platformVersion,
'appPackage': 'com.android.calculator2',
'appActivity': 'com.android.calculator2.Calculator',
'systemPort': int(systemPort)
}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
cal = Calculator(driver)
cal.add_numbers(2, 2)

In above code you can pass as many as device details that will run in parallel. But make sure you pass unique system port.

To run this you have to give below command




Python Appium - Step by step procedure for capturing screenshot

 Why To capture screenshot?

It is as important as your logs. If there is any failure for any test scenario, we can provide screenshot for the same. Its part of good practice to include in your test framework for each scenarios. Later point of time we will discuss about how to accommodate same in your framework.

Let us see how we can capture screenshot

1. Get the current activity name

2. Get the current time stamp

3. capture and save the screenshot at specified location

import pytest
from appium import webdriver
from login import Login
import time
import os
import base64
from calculator_app import Calculator


def __init__(self, driver):
self.driver = driver

def test_calculator_add_number():
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5556',
'deviceName': 'Pixel 2 API 28',
'platformVersion': '9.0',
'appPackage': 'com.android.calculator2',
'appActivity': 'com.android.calculator2.Calculator'
}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
cal = Calculator(driver)
cal.add_numbers(2, 2)
file_name = driver.current_activity + time.strftime("%Y_%m_%d_%H%M%S")
filepath = os.path.join("C:/Data/2021/May/Sample/", file_name + ".png")
driver.save_screenshot(filepath)


This the result from above script.






Python Appium - Procedure for Video/Screen Recording

 Why Screen/Video recording is required?

When there are complex test scenarios and if there is any failure it becomes easy to identify where exactly is the failure. As a tester point of view, it is always necessary to provide logs, video recording and screenshots (This may vary project to project). 

So let us start how to accommodate video recording in your appium framework.

Basically you would need three steps here,

1. start video recording

2. stop video recording

3. convert the captured video to mp3or mp4 format

So to record video you would need

driver.start_recording_screen()

To Stop video you would need to call

driver.start_recording_screen()

So here I will explain you with calculator example,

from appium import webdriver
from login import Login
import time
import os
import base64


def __init__(self, driver):
self.driver = driver

def test_calculator_click_number():
desired_caps = {
'platformName': 'android',
'udid': 'emulator-5556',
'deviceName': 'Pixel 2 API 28',
'platformVersion': '9.0',
'appPackage': 'com.android.calculator2',
'appActivity': 'com.android.calculator2.Calculator'
}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
driver.start_recording_screen()
login = Login(driver)
login.login_in_to_app()
video_rawdata = driver.stop_recording_screen()
video_name = driver.current_activity + time.strftime("%Y_%m_%d_%H%M%S")
filepath = os.path.join("C:/Data/2021/May/Sample/", video_name+".mp4")


with open(filepath,"wb+") as vd:
vd.write(base64.b64decode(video_rawdata))


Here whatever video has been recorded is stored in video_rawdata variable. Later at this point

with open(filepath,"wb+") as vd:
vd.write(base64.b64decode(video_rawdata))

It is been converted. In below code, video_name is the variable with current activity name and current time of video recording

video_name = driver.current_activity + time.strftime("%Y_%m_%d_%H%M%S")

In below code snippet we have stored the video at specified location

filepath = os.path.join("C:/Data/2021/May/Sample/", video_name+".mp4")


Feature Posts

Python Appium - Step by step procedure for capturing screenshot

 Why To capture screenshot? It is as important as your logs. If there is any failure for any test scenario, we can provide screenshot for th...