It has been more than a year since I wrote my first blog on mobile automation using Appium for Android applications. There were many requests to come up with a similar blog for iOS applications. But due to time constraints I couldn’t get to it. But as they say, better late than never! I finally have a guide to help you get started with iOS automation using Appium. By the end of this post you should be able to set up your Mac with Appium and run a test for an iOS app using a simulator.
Setup
Here are the steps to set up Xcode and Appium on Mac OS
Step1. Download Xcode
Xcode is an integrated development environment (IDE) containing a suite of software development tools developed by Apple for developing software for OS X and iOS. You need to download xcode and install it so that you can download the code for an iOS app and run it on a simulator.
Step2. Start an iOS emulator using Xcode
The Simulator app, available within Xcode, presents the iPhone, iPad or Apple Watch user interface in a window on your Mac computer. To launch simulator
1. Launch Xcode.
2. Do one of the following:
Note: You can also add simulators for any specific combination you want to test. For more details refer to this link
Step3. Download Appium client
Download the appium client. I downloaded version 1.4.13.
Step4. Install Python and Python client Library for Appium
There are several client libraries (in Java, Ruby, Python, PHP, JavaScript, and C#) which support Appium’s extensions to the WebDriver protocol. When using Appium, you want to use these client libraries instead of your regular WebDriver client. I have used the Appium Python client available here. Assuming you have pip installed on your machine, you can use the following command to install it
pip install Appium-Python-Client |
Step5. Start the Appium server console
Just double click on Appium file to start the appium server console. Then click on Launch button to start the Appium node server
Your first Test using Appium
For this test we will use a Table Search with the UISearchController app. We picked the UISearchController app because its source code was available to everyone and we could show you how to build the app and run it on a simulator. UISearchController is an iOS sample application that demonstrates how to use UISearchController.
Step1. Download the app to test
You can download the source code of Table Search with UISearchController app from here. The code is available in both swift and Objective-C language. We will be using the Swift code as it is a new programming language for iOS, OS X, watchOS, and tvOS apps that builds on the best of C and Objective-C.
Step2. Open the project using Xcode
Launch Xcode. Click on Open another project and select TableSearch-swift.xcodeproj inside the Swift folder to open the project
Step3. Build the app and run it using a simulator
Once you have the source code you can build the app and run it on the simulator. Just click on the “Build and then run the current scheme” button (Play icon near top left). Once the build completes you should get a build succeeded message and the app should open in the simulator selected.
Step4. Get the .app file and bundle id
To write your script, you will need the path of the .app file as well as a bundle id.
In case if you have already built and run the app on your simulator you may not need the .app file to run the test on the same simulator. Else you need the Search.swift.app file to run the test.
If you have run the app on a simulator you can find the .app file in below path
~/Library/Developer/CoreSimulator/Devices/{{Device Code}}/data/Containers/Bundle/Application |
To get the bundle id, right click on the Search.swift.app file and do a Show Package Contents to view the info.plist file.
Step5. Get the xpath of elements using Appium Inspector
Our test will search for an Apple device and click on it to view its details and then navigate back to the home screen of the application.
For this we need to find the xpath of different elements like Search field. We will use Appium Inspector for this.
Open the iOS setting in Appium, enter the App path of your test application and check the box next to it. Give the device information like device name and platform version. Now click on the Inspector icon at the top as shown in the screenshot below
Note: Uncheck the BundelID field as i had issues when trying to launch appium Inspector with it populated
The Appium Inspector will get launched. Now you can find any element and it’s name by either clicking the element on the preview page provided, or locating it in the UI navigator. In this test, I’m looking for the details of Search box field.
Now we can use the details to build my xpath for Search field. Similarly you can use the appium Inspector to view the details of any field in the App.
Xpath of Search field = “//UIASearchBar[@name=’Search’]”
Step6. Write the test
Create a test script (Table_Search.py) in $Directory_Of_My_Choice based on the snippet below. I have my Search.swift.app file inside the ‘TableSearchwithUISearchController/Swift’ folder within my $Directoy_Of_My_Choice. I have hard coded the path in this example but you can use a combination of os.path.abspath and os.path.dirname(__file__) to make it a path relative to your test file.
Also pay particular attention to the setup() method. You will run into all sorts of cryptic errors if the platformVersion, deviceName and bundleId are not set correctly.
""" Simple iOS tests for table search app. NOTE: This is throwaway code to help testers get started with iOS automation """ import unittest import os from appium import webdriver from time import sleep class TableSearchTest(unittest.TestCase): def setUp(self): # Set up appium app = os.path.join(os.path.dirname(__file__), 'TableSearchwithUISearchController/Swift', 'Search.swift.app') app = os.path.abspath(app) self.driver = webdriver.Remote( command_executor='http://127.0.0.1:4723/wd/hub', desired_capabilities={ 'app': app, 'platformName': 'iOS', 'platformVersion': '9.2', 'deviceName': 'iPhone 5s', 'bundleId':'com.example.apple-samplecode.Search-swift' }) def test_search_field(self): # Search for an Apple device and click on it to view the details and navigate back # Find the search element and perform send keys action search_element = self.driver.find_element_by_xpath("//UIASearchBar[@name='Search']") search_element.send_keys("iPad") sleep(2) # Get the xpath of first element first_element = self.driver.find_element_by_xpath("//UIAApplication[1]/UIAWindow[1]/UIATableView[1]/UIATableCell[1]/UIAStaticText[1]") # Assert that the text matches self.assertEqual('iPad', first_element.get_attribute('name')) # Perform click action first_element.click() sleep(2) # Click on search element self.driver.find_element_by_name("Search").click() def tearDown(self): self.driver.quit() if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromTestCase(TableSearchTest) unittest.TextTestRunner(verbosity=2).run(suite) |
Step7. Run the test and check result
Run the script Table_Search.py file. The Table Search application is launched in the simulator and test runs.
There you have it – a tutorial to get you started with iOS automation using Appium and Python.
Hire technical testers from Qxf2
Qxf2 prides itself on being the home of technical testers who are experts in automation and testing strategy. We specialize in helping startups manage their testing efficiently. Looking for QA consultants who can deliver results from day one? Explore our range of QA services designed for startups, and let’s discuss how we can assist your team.
I am a dedicated quality assurance professional with a true passion for ensuring product quality and driving efficient testing processes. Throughout my career, I have gained extensive expertise in various testing domains, showcasing my versatility in testing diverse applications such as CRM, Web, Mobile, Database, and Machine Learning-based applications. What sets me apart is my ability to develop robust test scripts, ensure comprehensive test coverage, and efficiently report defects. With experience in managing teams and leading testing-related activities, I foster collaboration and drive efficiency within projects. Proficient in tools like Selenium, Appium, Mechanize, Requests, Postman, Runscope, Gatling, Locust, Jenkins, CircleCI, Docker, and Grafana, I stay up-to-date with the latest advancements in the field to deliver exceptional software products. Outside of work, I find joy and inspiration in sports, maintaining a balanced lifestyle.
Hi Avinash Sir,
Kindly help in below question
How do i launch Chrome browser in Appium Python Language ?
Regards
Raj
Hi Raj,
I have not run any tests as of today in chrome browser in a mobile device. But i guess the below link should help you out here
http://appium.io/slate/en/v1.3.4/?python#python-example
Regards
Avinash Shetty
Is this solution working on iOS10?
Appium had some issues with iOS 10 on launch
I guess we can use appium with iOS 10. As per the link below its mentioned “To test an app in iOS 10 with Appium, we have to use Xcode 8 or newer with Appium 1.6 or newer.”
https://medium.com/@chenchaoyi/the-options-of-inspecting-ios-10-app-with-appium-1-6-534ba166b958#.k1g264d2l
I tried this solution with iOS 10.3 and I failed to set it up. I got error:
[XCUITest] Error: Could not find app at ‘~/Library/Developer/CoreSimulator/Devices/1D37B64A-35B3-4964-AE78-6292ECC35B7F/data/Containers/Bundle/Application/D8373724-B232-4C9B-96C2-A5D19FD4A4B4/Search.swift.app’ which I was not able to resolve
Hi,
Can you cross check the path where you have the Search.swift.app file. Is it inside “~/Library/Developer/CoreSimulator/Devices/1D37B64A-35B3-4964-AE78-6292ECC35B7F/data/Containers/Bundle/Application/D8373724-B232-4C9B-96C2-A5D19FD4A4B4/” folder
Thanks & Regards
Avinash Shetty
Hi Avinash,
Great information. Thanks for sharing. Would like to know if Appium will support all versions of Swift, how developed is the appium framework with Swift vs Objective C and how fast is appium when compared to xctest.
Thanks
PP
Hi,
I have tried iOS apps developed using Swift and it worked fine for me. A bit of research also showed me that there’s also a swift selenium client: https://github.com/appium/selenium-swift, which I haven’t tried still. Also after deprecation of UIAutomation in Xcode, there is an Appium iOS driver, backed by Apple XCUITest (https://github.com/appium/appium-xcuitest-driver).
I haven’t used XCTest so I really can’t tell which is faster between appium and XCTest, but I feel XCTest would be faster than many external frameworks.
Appium server is up and running
I’ve changed localhost:4723 to 127.0.0.1:4723
app = os.path.join(os.path.dirname(__file__),
‘/Users/project.deal/Desktop/Gauge.app’)
Appium works well but i tried to run on pycharm , there is no log on appium and it returns this error =>
BasicTest.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../Library/Python/2.7/lib/python/site-packages/appium/webdriver/webdriver.py:37: in __init__
super(WebDriver, self).__init__(command_executor, desired_capabilities, browser_profile, proxy, keep_alive)
/usr/local/lib/python2.7/site-packages/selenium/webdriver/remote/webdriver.py:98: in __init__
self.start_session(desired_capabilities, browser_profile)
/usr/local/lib/python2.7/site-packages/selenium/webdriver/remote/webdriver.py:188: in start_session
response = self.execute(Command.NEW_SESSION, parameters)
/usr/local/lib/python2.7/site-packages/selenium/webdriver/remote/webdriver.py:252: in execute
self.error_handler.check_response(response)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
response = {‘status’: 500, ‘value’: ”}
def check_response(self, response):
“””
Checks that a JSON response from the WebDriver does not have an error.
:Args:
– response – The JSON response from the WebDriver server as a dictionary
object.
:Raises: If the response contains an error message.
“””
status = response.get(‘status’, None)
if status is None or status == ErrorCode.SUCCESS:
return
value = None
message = response.get(“message”, “”)
screen = response.get(“screen”, “”)
stacktrace = None
if isinstance(status, int):
value_json = response.get(‘value’, None)
if value_json and isinstance(value_json, basestring):
import json
try:
value = json.loads(value_json)
if len(value.keys()) == 1:
value = value[‘value’]
status = value.get(‘error’, None)
if status is None:
status = value[“status”]
message = value[“value”]
if not isinstance(message, basestring):
value = message
message = message.get(‘message’)
else:
message = value.get(‘message’, None)
except ValueError:
pass
exception_class = ErrorInResponseException
if status in ErrorCode.NO_SUCH_ELEMENT:
exception_class = NoSuchElementException
elif status in ErrorCode.NO_SUCH_FRAME:
exception_class = NoSuchFrameException
elif status in ErrorCode.NO_SUCH_WINDOW:
exception_class = NoSuchWindowException
elif status in ErrorCode.STALE_ELEMENT_REFERENCE:
exception_class = StaleElementReferenceException
elif status in ErrorCode.ELEMENT_NOT_VISIBLE:
exception_class = ElementNotVisibleException
elif status in ErrorCode.INVALID_ELEMENT_STATE:
exception_class = InvalidElementStateException
elif status in ErrorCode.INVALID_SELECTOR \
or status in ErrorCode.INVALID_XPATH_SELECTOR \
or status in ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPER:
exception_class = InvalidSelectorException
elif status in ErrorCode.ELEMENT_IS_NOT_SELECTABLE:
exception_class = ElementNotSelectableException
elif status in ErrorCode.ELEMENT_NOT_INTERACTABLE:
exception_class = ElementNotInteractableException
elif status in ErrorCode.INVALID_COOKIE_DOMAIN:
exception_class = WebDriverException
elif status in ErrorCode.UNABLE_TO_SET_COOKIE:
exception_class = WebDriverException
elif status in ErrorCode.TIMEOUT:
exception_class = TimeoutException
elif status in ErrorCode.SCRIPT_TIMEOUT:
exception_class = TimeoutException
elif status in ErrorCode.UNKNOWN_ERROR:
exception_class = WebDriverException
elif status in ErrorCode.UNEXPECTED_ALERT_OPEN:
exception_class = UnexpectedAlertPresentException
elif status in ErrorCode.NO_ALERT_OPEN:
exception_class = NoAlertPresentException
elif status in ErrorCode.IME_NOT_AVAILABLE:
exception_class = ImeNotAvailableException
elif status in ErrorCode.IME_ENGINE_ACTIVATION_FAILED:
exception_class = ImeActivationFailedException
elif status in ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS:
exception_class = MoveTargetOutOfBoundsException
else:
exception_class = WebDriverException
if value == ” or value is None:
value = response[‘value’]
if isinstance(value, basestring):
if exception_class == ErrorInResponseException:
raise exception_class(response, value)
> raise exception_class(value)
E WebDriverException: Message:
/usr/local/lib/python2.7/site-packages/selenium/webdriver/remote/errorhandler.py:165: WebDriverException
=================================== FAILURES ===================================
_________________________ BasicTest.test_search_field __________________________
self =
def setUp(self):
# Set up appium
app = os.path.join(os.path.dirname(__file__),
‘/Users/muge.karakas/Desktop/Bilyoner.app’)
app = os.path.abspath(app)
self.driver = webdriver.Remote(
command_executor=’http://localhost:4723/wd/hub’,
desired_capabilities={
‘app’: app,
‘platformName’: ‘iOS’,
‘platformVersion’: ‘9.3’,
> ‘deviceName’: ‘yyy’
})
BasicTest.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../Library/Python/2.7/lib/python/site-packages/appium/webdriver/webdriver.py:37: in __init__
super(WebDriver, self).__init__(command_executor, desired_capabilities, browser_profile, proxy, keep_alive)
/usr/local/lib/python2.7/site-packages/selenium/webdriver/remote/webdriver.py:98: in __init__
self.start_session(desired_capabilities, browser_profile)
/usr/local/lib/python2.7/site-packages/selenium/webdriver/remote/webdriver.py:188: in start_session
response = self.execute(Command.NEW_SESSION, parameters)
/usr/local/lib/python2.7/site-packages/selenium/webdriver/remote/webdriver.py:252: in execute
self.error_handler.check_response(response)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
response = {‘status’: 500, ‘value’: ”}
def check_response(self, response):
“””
Checks that a JSON response from the WebDriver does not have an error.
:Args:
– response – The JSON response from the WebDriver server as a dictionary
object.
:Raises: If the response contains an error message.
“””
status = response.get(‘status’, None)
if status is None or status == ErrorCode.SUCCESS:
return
value = None
message = response.get(“message”, “”)
screen = response.get(“screen”, “”)
stacktrace = None
if isinstance(status, int):
value_json = response.get(‘value’, None)
if value_json and isinstance(value_json, basestring):
import json
try:
value = json.loads(value_json)
if len(value.keys()) == 1:
value = value[‘value’]
status = value.get(‘error’, None)
if status is None:
status = value[“status”]
message = value[“value”]
if not isinstance(message, basestring):
value = message
message = message.get(‘message’)
else:
message = value.get(‘message’, None)
except ValueError:
pass
exception_class = ErrorInResponseException
if status in ErrorCode.NO_SUCH_ELEMENT:
exception_class = NoSuchElementException
elif status in ErrorCode.NO_SUCH_FRAME:
exception_class = NoSuchFrameException
elif status in ErrorCode.NO_SUCH_WINDOW:
exception_class = NoSuchWindowException
elif status in ErrorCode.STALE_ELEMENT_REFERENCE:
exception_class = StaleElementReferenceException
elif status in ErrorCode.ELEMENT_NOT_VISIBLE:
exception_class = ElementNotVisibleException
elif status in ErrorCode.INVALID_ELEMENT_STATE:
exception_class = InvalidElementStateException
elif status in ErrorCode.INVALID_SELECTOR \
or status in ErrorCode.INVALID_XPATH_SELECTOR \
or status in ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPER:
exception_class = InvalidSelectorException
elif status in ErrorCode.ELEMENT_IS_NOT_SELECTABLE:
exception_class = ElementNotSelectableException
elif status in ErrorCode.ELEMENT_NOT_INTERACTABLE:
exception_class = ElementNotInteractableException
elif status in ErrorCode.INVALID_COOKIE_DOMAIN:
exception_class = WebDriverException
elif status in ErrorCode.UNABLE_TO_SET_COOKIE:
exception_class = WebDriverException
elif status in ErrorCode.TIMEOUT:
exception_class = TimeoutException
elif status in ErrorCode.SCRIPT_TIMEOUT:
exception_class = TimeoutException
elif status in ErrorCode.UNKNOWN_ERROR:
exception_class = WebDriverException
elif status in ErrorCode.UNEXPECTED_ALERT_OPEN:
exception_class = UnexpectedAlertPresentException
elif status in ErrorCode.NO_ALERT_OPEN:
exception_class = NoAlertPresentException
elif status in ErrorCode.IME_NOT_AVAILABLE:
exception_class = ImeNotAvailableException
elif status in ErrorCode.IME_ENGINE_ACTIVATION_FAILED:
exception_class = ImeActivationFailedException
elif status in ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS:
exception_class = MoveTargetOutOfBoundsException
else:
exception_class = WebDriverException
if value == ” or value is None:
value = response[‘value’]
if isinstance(value, basestring):
if exception_class == ErrorInResponseException:
raise exception_class(response, value)
> raise exception_class(value)
E WebDriverException: Message:
/usr/local/lib/python2.7/site-packages/selenium/webdriver/remote/errorhandler.py:165: WebDriverException
=========================== 1 failed in 0.20 seconds ===========================
Process finished with exit code 0
I think you have the path set wrong. For example. are you sure this line is correct?
What this code will do is to figure out:
a) the directory of the Python file that has this line (e.g.: /users/mge)
b) append ‘/Users/project.deal/Desktop/Gauge.app’ to the value in a) (So: /users/mge/Users/project.deal/Desktop/Gauge.app)
To debug you can do one of two things:
a) Print out the variable app and see if the path looks correct
b) Hardcode app to the exact path of the .app file
Both a) and b) will tell you if this piece of code is the problem.
i solved this problem. Maybe others will be have this problem later. it’s selenium version issue. i’ve last version 3.4.3 then i’ve downgraded selenium version 2.53.6 errors disappear.
Thanks for sharing the information, it’s good that your problem got solved by downgrading selenium version
You are right. Correct path is /Users/muge.karakas/Desktop/Bilyoner.app. i wrote wrong it here. As a result i have still problem.