Testing A Hybrid Mobile App Using Appium

Sauce AI for Test Authoring: Move from intent to execution in minutes.|xBack to ResourcesBlogPosted

April 05, 2026 · 16 min read · Mobile Testing

Sauce AI for Test Authoring: Move from intent to execution in minutes.

|

x

Back to Resources

Blog

Posted September 29, 2017

Testing A Hybrid Mobile App Using Appium

quote

If you want to test hybrid mobile apps—or any other kind of app, for that matter—Appium is a great pick. In this article, I provide an Appium example of testing a hybrid mobile app built with React.

Appium ’ s Versatility: Mobile Testing and Beyond

First, though, a few words on why Appium is a great option for hybrid roving app testing. Appium is an open source mechanization testing framework for use with native and hybrid mobile apps. It direct to be words and framework agnostic—meaning it can work with any lyric and prove framework of your choice.

Once you take a language, more often than not, Appium work with it. Appium can be utilize to test iOS, Android and Windows applications, still if it & # x27; s hybrid. Appium aims to automate any roving app from any language and any test fabric, with full access to backend APIs and DBs from test codification. It does this by using theWebDriver protocolwhich is a control interface that enable programs to remotely instruct the behavior of web browsers.

To show how versatile Appium is, in this article we ’ ll use it for a scenario that is somewhat off the beaten path: Testing a React Native intercrossed app using the py.test fabric.

My Hybrid Mobile App

My hybrid app is compose in ReactJS, and my test suit are written in Python. They all work together by communicating with the Appium WebDriver client.

This driver connects to the Appium service that runs in the ground on your local machine. Appium is so full-bodied that you can also choose to not run the service in your local machine! You can just connect to the Sauce Labs cloud-based mobile testing platform and leave it up to them to maintain all of the emulators, simulators and existent device you would like to try against.

Getting Started with Appium

To kick things off, let & # x27; s establish Appium.

1
brew install libimobiledevice -- HEAD
2
brew install carthage
3
npm install -g appium
4
npm install wd
5
npm install -g ios-deploy
6


I & # x27; m installing Appium on MacOS with ` brew ` and ` node ` already install.

Now let & # x27; s start the Appium service with theappium command.

You should now see:


[Appium] Welcome to Appium v1.6.3
[Appium] Appium REST http interface hearer started on 0.0.0.0:4723

Connecting the Puzzle

Alright. We hold Appium running. Now what?

All we have to do is write trial cases in whatever language and framework we choose and connect them to Appium. I & # x27; m opt Python and ` py.test `. You can besides choose Javascript and Mocha. But I prefer Python. I & # x27; ll be testing a React Native hybrid mobile app compiled to both iOS (.app) and Android (.apk).

The test cases instruct Appium to fill in text boxes, click on buttons, check substance on the screen, and even wait a specific amount of clip for a screen to load. If at any clip Appium is unable to find elements, it will throw an exception and your test fails.

Let & # x27; s part testing!

Creating a React Native Hybrid Mobile App

You can fetch the dummy application from myGitHub

The app contains two text fields, one for username and one for password, and besides a button to “ log in. ” For the purposes of this article, the login function does nil, absolutely nothing!

SUSA automates exploratory testing with persona-driven behavior, catching bugs that scripted automation misses.

Writing Tests

Let & # x27; s initiative get sure to have ` pytest ` and the Python Appium Client installed. It & # x27; s as bare as:


pip install pytest
pip install Appium-Python-Client

Essentially, ` py.test ` will connect to the Appium service and launch the coating, either on the simulator or physical twist. You will receive to provide the Appium endpoint and the emplacement of the ` .app ` or ` .apk ` file. You can too delimit the device you & # x27; d like to test it on.

Creating a Base Test Class

Let & # x27; s make a base test category that handles high-level use such as connecting to the Appium service and terminating the connection.

1
importunittest
2
from appium import webdriver
3
classAppiumTest(unittest.TestCase):
4
def setUp(self):
5
self.driver= webdriver.Remote(
6
command_executor=& # x27; http: //127.0.0.1:4723/wd/hub & # x27;,
7
desired_capabilities={
8
& # x27; app & # x27;:& # x27; PATH_OF_.APP_FILE & # x27;,
9
& # x27; platformName & # x27;:& # x27; iOS & # x27;,
10
& # x27; deviceName & # x27;:& # x27; iPhone Simulator & # x27;,
11
& # x27; automationName & # x27;:& # x27; XCUITest & # x27;,
12
})
13
def tearDown(self):
14
self.driver.quit()

Selecting Elements

How do we recount Appium to chatter on this button or filling in this form? We do this by identifying the elements either with classnames, IDs, XPaths, accessibility label, or more.

We & # x27; ll use both XPaths and availability labels here, since React Native hasnot yet implemented IDs.

Finding Elements Using XPaths

You can find an element with two of its dimension: its selector and name. For example, if your TextView called “ Welcome to Appium, ” the selector would be “ text ” and “ Welcome To Appium ” would be used as the identifier.


driver.find_element_by_xpath (& # x27; // * [@ text= & quot; Welcome To Appium & quot;] & # x27;)

This chooser looks for a DOM element that has a text attribute of “ Welcome To Appium. ”

It & # x27; s no shocker that this might not be the alone TextView element with “ Welcome To Appium. ” What happens when there are multiple element? You can so use the function ` find_elements_by_xpath `, which returns a lean of the elements that match your query.

And it & # x27; s also no shocker that these values undergo uninterrupted alteration. It would not be fun to rewrite examination for every minor modification that happens in the app. That & # x27; s where accessibility labels come in.

Finding Elements Using Accessibility Labels

A much more stable pick is to find elements apply their accessibility labels. These rarely get alter during development. However, in React Native, availability labels can only be added to View elements. The workaround is to wrap any element you will need to test in a View element.


& lt; View accessibilityLabel= & quot; Welcome To Appium & quot; & gt;
& lt; Text & gt; Welcome To Appium & lt; /Text & gt;
& lt; /View & gt;

Do note that handiness labels are read by screen readers, so make sure to name reasonably.

You can now access the ingredient like this:

driver.find_elements_by_accessibility_id (& quot; Welcome To Appium & quot;)

Transitioning Between Pages

The almost mutual thing you & # x27; ll see in examination cases on either Appium or Selenium is the immense turn of sleep argument. This is because the tryout cases you write will experience no knowledge or binding to a screen.

Say your application has a button on one page and a form on another. And this button is utilise to transition from one page to another. You will want your test example to tick on a button, transition the page, and then fill in a form. However, your test case will never know that it just transitioned between two Page! The webdriver protocol can only access elements on a page, and not a page itself. Because of this, sopor for an arbitrary amount of time is very common when you expect a page to transition. However, this is very rudimentary. With Appium, we can alternatively instruct it to & # x27; wait_until & # x27; an element is on a page.

Finally, Let ’ s Write Some Tests!

You can find all the tryout cases on myGitHub repo.

Here’s how it looksfor an iOS React Native app:

1
import os
2
import unittest
3
import time
4
from appium import webdriver
5
import xml
6
classAppiumTest(unittest.TestCase):
7
defsetUp(self):
8
self.driver = webdriver.Remote(
9
command_executor=& # x27; http: //127.0.0.1:4723/wd/hub & # x27;,
10
desired_capabilities={
11
& # x27; app & # x27;: os.path.abspath(APP_PATH),
12
& # x27; platformName & # x27;:& # x27; iOS & # x27;,
13
& # x27; deviceName & # x27;:& # x27; iPhone Simulator & # x27;,
14
& # x27; automationName & # x27;:& # x27; XCUITest & # x27;,
15
})
16
deftearDown(self):
17
self.driver.quit()
18
defrepl(self):
19
import pdb; pdb.set_trace()
20
defdump_page(self):
21
withopen(& # x27; appium_page.xml & # x27;,& # x27; w & # x27;)as f:
22
raw = self.driver.page_source
23
ifnot raw:
24
return
25
source = xml.dom.minidom.parseString(raw.encode(& # x27; utf8 & # x27;))
26
f.write(source.toprettyxml())
27
def_get(self, text, index=None, partial=False):
28
selector =& quot; name & quot;
29
if text.startswith(& # x27; # & # x27;):
30
elements = self.driver.find_elements_by_accessibility_id(text[1:])
31
elif partial:
32
elements = self.driver.find_elements_by_xpath(& # x27; // * [contains (@ % s, & quot; % s & quot;)] & # x27;%(selector, text))
33
else:
34
elements = self.driver.find_elements_by_xpath(& # x27; // * [@ % s= & quot; % s & quot;] & # x27;%(selector, text))
35
ifnot elements:
36
raise Exception()
37
if index:
38
return elements[index]
39
if index isNoneandlen(elements)>1:
40
raiseIndexError(& # x27; More that one element found for % r & # x27;% text)
41
return elements[0]
42
defget(self, text,*args,**kwargs):
43
& # x27; & # x27; & # x27; try to get for X seconds; composition over loading waits/sleeps & # x27; & # x27; & # x27;
44
timeout_seconds= kwargs.get(& # x27; timeout_seconds & # x27;,10)
45
start = time.time()
46
while time.time()- start <timeout_seconds:
47
try:
48
return self._get(text,*args,**kwargs)
49
exceptIndexError:
50
raise
51
except:
52
pass
53
# self.wait (.2)
54
time.sleep(.2)
55
raise Exception(& # x27; Could not find text % r after % r seconds & # x27;%(
56
text,timeout_seconds))
57
defwait_until(self,*args,**kwargs):
58
# only care if there is at least one match
59
return self.get(*args, index=0,**kwargs)
60
classExampleTests(AppiumTest):
61
deftest_loginError(self):
62
self.dump_page()
63
self.wait_until(& # x27; Login & # x27;, partial=True)
64
self.get(& # x27; Please enter your e-mail & # x27;).send_keys(& # x27; foo @ example.com\n & # x27;)
65
self.get(& # x27; Please enter your password & # x27;).send_keys(& # x27; Password1 & # x27;)
66
self.driver.hide_keyboard()
67
self.get(& # x27; Press me to submit & # x27;, index=1).click()
68
self.wait_until(& # x27; Please check your credentials & # x27;)
69
assertTrue
70
deftest_loginSuccess(self):
71
self.dump_page()
72
self.wait_until(& # x27; Login & # x27;, partial=True)
73
self.get(& # x27; Please enter your email & # x27;).send_keys(& # x27; dummyemail @ example.com\n & # x27;)
74
self.get(& # x27; Please inscribe your password & # x27;).send_keys(& # x27; 121212 & # x27;)
75
self.driver.hide_keyboard()
76
self.get(& # x27; Press me to submit & # x27;, index=1).click()
77
self.wait_until(& # x27; Login Successful & # x27;)
78
assertTrue
79
deftest_loginEmptyEmail(self):
80
self.dump_page()
81
self.wait_until(& # x27; Login & # x27;, partial=True)
82
self.get(& # x27; Please enter your email & # x27;).send_keys(& # x27; \n & # x27;)
83
self.get(& # x27; Please enter your password & # x27;).send_keys(& # x27; 121212 & # x27;)
84
self.driver.hide_keyboard()
85
self.get(& # x27; Press me to subject & # x27;, index=1).click()
86
self.wait_until(& # x27; Please enter your e-mail ID & # x27;)
87
assertTrue
88
deftest_loginEmptyPassword(self):
89
self.dump_page()
90
self.wait_until(& # x27; Login & # x27;, partial=True)
91
self.get(& # x27; Please enter your email & # x27;).send_keys(& # x27; dummyemail @ example.com\n & # x27;)
92
self.get(& # x27; Please enrol your password & # x27;).send_keys(& # x27; & # x27;)
93
self.driver.hide_keyboard()
94
self.get(& # x27; Press me to submit & # x27;, index=1).click()
95
self.wait_until(& # x27; Please enter your password & # x27;)
96
assertTrue
97

And here ’ s how it seem for an Android React Native app:

1
import os
2
import unittest
3
import time
4
from appium import webdriver
5
import xml
6
classAppiumTest(unittest.TestCase):
7
defsetUp(self):
8
abs_path = os.path.abspath(APK_PATH)
9
self.driver = webdriver.Remote(
10
command_executor=& # x27; http: //127.0.0.1:4723/wd/hub & # x27;,
11
desired_capabilities={
12
& # x27; app & # x27;: os.path.abspath(abs_path),
13
& # x27; platformName & # x27;:& # x27; Android & # x27;,
14
& # x27; deviceName & # x27;:& # x27; Nexus 6P API 25 & # x27;,
15
})
16
deftearDown(self):
17
self.driver.quit()
18
defrepl(self):
19
import pdb; pdb.set_trace()
20
defdump_page(self):
21
withopen(& # x27; appium_page.xml & # x27;,& # x27; w & # x27;)as f:
22
raw = self.driver.page_source
23
ifnot raw:
24
return
25
source = xml.dom.minidom.parseString(raw.encode(& # x27; utf8 & # x27;))
26
f.write(source.toprettyxml())
27
def_get(self, text, index=None, partial=False):
28
selector =& quot; content-desc & quot;
29
if text.startswith(& # x27; # & # x27;):
30
elements = self.driver.find_elements_by_accessibility_id(text[0:])
31
elif partial:
32
elements = self.driver.find_elements_by_xpath(& # x27; // * [contains (@ % s, & quot; % s & quot;)] & # x27;%(selector, text))
33
else:
34
elements = self.driver.find_elements_by_xpath(& # x27; // * [@ % s= & quot; % s & quot;] & # x27;%(selector, text))
35
ifnot elements:
36
raise Exception()
37
if index:
38
return elements[index]
39
if index isNoneandlen(elements)>1:
40
raiseIndexError(& # x27; More that one element found for % r & # x27;% text)
41
return elements[0]
42
defget(self, text,*args,**kwargs):
43
timeout_seconds= kwargs.get(& # x27; timeout_seconds & # x27;,10)
44
start = time.time()
45
while time.time()- start <timeout_seconds:
46
try:
47
return self._get(text,*args,**kwargs)
48
exceptIndexError:
49
raise
50
except:
51
pass
52
# self.wait (.2)
53
time.sleep(.2)
54
raise Exception(& # x27; Could not find text % r after % r seconds & # x27;%(
55
text,timeout_seconds))
56
defwait_until(self,*args,**kwargs):
57
# only caution if there is at least one match
58
return self.get(*args, index=0,**kwargs)
59
classExampleTests(AppiumTest):
60
deftest_loginError(self):
61
time.sleep(5)
62
self.dump_page()
63
self.wait_until(& # x27; Please enter your email & # x27;, partial=False)
64
self.get(& # x27; Please recruit your e-mail & # x27;).send_keys(& # x27; foo @ example.com\n & # x27;)
65
self.get(& # x27; Please enter your password & # x27;).send_keys(& # x27; Password1 & # x27;)
66
self.driver.hide_keyboard()
67
self.get(& # x27; Press me to state & # x27;, index=0).click()
68
self.wait_until(& # x27; Please ascertain your credentials & # x27;)
69
assertTrue
70
deftest_loginSuccess(self):
71
time.sleep(5)
72
self.dump_page()
73
self.wait_until(& # x27; Please enter your email & # x27;, partial=False)
74
self.get(& # x27; Please participate your email & # x27;).send_keys(& # x27; dummyemail @ example.com\n & # x27;)
75
self.get(& # x27; Please enter your password & # x27;).send_keys(& # x27; 121212 & # x27;)
76
self.driver.hide_keyboard()
77
self.get(& # x27; Press me to submit & # x27;, index=0).click()
78
self.wait_until(& # x27; Login Successful & # x27;)
79
assertTrue
80
deftest_loginEmptyEmail(self):
81
time.sleep(5)
82
self.dump_page()
83
self.wait_until(& # x27; Please enter your e-mail & # x27;, partial=False)
84
self.get(& # x27; Please inscribe your email & # x27;).send_keys(& # x27; \n & # x27;)
85
self.get(& # x27; Please enter your countersign & # x27;).send_keys(& # x27; 121212 & # x27;)
86
self.driver.hide_keyboard()
87
self.get(& # x27; Press me to state & # x27;)
88


Published:
Sep 29, 2017
Share this post
Copy Share Link
LinkedIn
© 2026 Sauce Labs Inc., all rights earmark. SAUCE and SAUCE LABS are file trademarks owned by Sauce Labs Inc. in the United States, EU, and may be registered in other jurisdiction.
robot
quote

Automate This With SUSA

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts needed.

Try SUSA Free

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free