Appium Bootcamp – Chapter 5: Writing and Refactoring Your Tests
Sauce AI for Test Authoring: Move from intent to execution in minutes.|xBack to ResourcesBlogPosted
Sauce AI for Test Authoring: Move from intent to execution in minutes.
|
x
Blog
Appium Bootcamp – Chapter 5: Writing and Refactoring Your Tests
[UPDATE- November 2019]You can find the novel Get Started with Appium white papers here:Get Started with Appium- Java and Get Started with Appium- Ruby
This is the fifth post in a series telephone Appium Bootcamp by noted Selenium expertDave Haeffner.
Dave lately immerse himself in the open germ Appium project and cooperate with leading Appium contributor Matthew Edwards to bring us this material. Appium Bootcamp is for those who are brand new to wandering exam automation with Appium. No familiarity with Selenium is require, although it may be utilitarian. This is the fifth of eight posts; two new station will be released each workweek.
Now that we & # x27; ve identified some trial action in our apps, let & # x27; s put them to act by wiring them up in code.
We & # x27; ll start with the iOS app and then locomote onto Android. But maiden, we & # x27; ll need to do a quick bit of setup.
Quick Setup
Since we & # x27; re setting up our test code from scratch, we & # x27; ll take to make sure we have the necessary gems installed -- and done so in a way that is quotable (which will arrive in handy for other team appendage and for use with Continuous Integration).
In Ruby, this is leisurely to do withBundler. With it you can determine a list of gem and their variation to instal and update from for your project.
Install Bundler by runninggem install bundlerfrom the command-line and then make a file calledGemfilewith the following contents:
# filename: Gemfile
source & # x27; https: //rubygems.org & # x27;
gem & # x27; rspec & # x27;, & # x27; ~ & gt; 3.0.0 & # x27;
gem & # x27; appium_lib & # x27;, & # x27; ~ & gt; 4.0.0 & # x27;
gem & # x27; appium_console & # x27;, & # x27; ~ & gt; 1.0.1 & # x27;
After make theGemfile run bundle install. This will make certainrspec(our testing framework),appium_lib(the Appium Ruby bindings), andappium_console(our interactive test console) are instal and ready for use in this directory.
Capabilities
In order to run our tests, we will need to delineate the capabilities of our app. We can either do this in our test codification, or we can leverage theappium.txtfiles we used for the Appium Console.
Let & # x27; s do the latter approaching. But initiatory, we & # x27; ll want to make two new folders; one for Android and another for iOS. Once they & # x27; re created, let & # x27; s place each of theappium.txtfile into their several folders.
├── Gemfile
├── Gemfile.lock
├── android
│ └── appium.txt
└── ios
└── appium.txt
Be sure to update theappcapability in yourappium.txtfiles if you & # x27; re employ a relative path.
Writing Your Initiatory Test
With our initial frame-up taken aid of, let & # x27; s make our inaugural test file (a.k.a. & quot; spec & quot; in RSpec). The test actions we name in the old post were concentre on piloting in the app. So let & # x27; s ring this specification filenavigation_spec.rband place it in theios folder.
├── Gemfile
├── Gemfile.lock
├── android
│ └── appium.txt
└── ios
└── appium.txt
└── navigation_spec.rb
Now let & # x27; s write our test to launch Appium for iOS and perform a uncomplicated navigation test.
In RSpec, describerefer the beginning of a test file, whereasitannounce a test. So what we feature is a test file with a individual tryout in it.
In this test file, we are part our Appium session before each tryout (e.g.,before (: each)) and cease it after each tryout (e.g.,after (: each)). More specifically, inbefore (: each), we are finding the route to the iOSappium.txtfile and then loading it. After that we start the Appium session and promote the Appium command so they will be available for use within our test. We so supplydriver_quit in after (: each)to flawlessly end the Appium session. This is equivalent to submitting anxcommand in the Appium console.
The bidding in our exam (it & # x27; First cell & # x27; do) should seem conversant from the last billet. We & # x27; re happen the initiatory cell, grab it & # x27; s title, click on the cell, and then looking to see if the title appeared on the inner blind.
After saving this file, let & # x27; s change directories into theiosfolder (e.g.,cd ios), and run the exam (assuming your Appium Server is running -- if not, charge up the Appum GUI and clickLaunch) with rspec navigation_spec.rb. When it & # x27; s running, you will see the iOS simulator launch, lade up the test app, tick the first cell, and so close.
This is a good showtime, but we can clean this code up a bit by leveraging some simple page objects and a central configuration.
A Page Objects Primer
Machine-driven examination can quickly turn brittle and hard to hold. This is largely due to the fact that we are testing functionality that will constantly change. In order to battle this, we can use page target.
Page Objects are simple objects that model the behavior of an covering. So instead than write your tests directly against your app, you can indite them against these aim. This will make your test codification more reusable, maintainable, and leisurely to fix when the app changes.
Refactoring Your First Test
Let & # x27; s create a new directory calledpageswithin ouriosdirectory and create two new file in it:home.rb and inner_screen.rb. And while we & # x27; re at it, let & # x27; s create a new folder to store our test file (namespec-- which is a brochure RSpec will know to look for at run clip) and go ournavigation_spec.rb into it.
├── Gemfile
├── Gemfile.lock
├── android
│ └── appium.txt
└── ios
├── appium.txt
├── pages
│ ├── home.rb
│ └── inner_screen.rb
└── spec
├── navigation_spec.rb
Let & # x27; s open upios/pages/home.rbto create our first page aim.
# filename: ios/pages/home.rb
faculty Pages
module Home
course & lt; & lt; self
def first_cell
@ found_cell = waiting {text 2}
self
end
def title
@ found_cell.name.split (& # x27;, & # x27;) .first
end
def click
@ found_cell.click
end
end
end
end
module Kernel
def home
Pages: :Home
end
end
Since the Appium commands are getting advertise for use (instead of passing around a driver object), storing our page objects in a faculty is a light approach (instead than keeping them in a class that we would want to instantiate).
To create theHomemodule we foremost wrap it in another module calledPages. This helps prevent any namespace collisions as well simplify the promotion of Appium methods.
In Home, we & # x27; ve created some simple static methods to mime the demeanor of the home screen (e.g.,first_cell, title, click). By storing the found cell in an instance variable (e.g.,@ found_cell) and returningself, we will be able to chain these method together in our test (e.g.,first_cell.title). And in order to cleanly cite the page object in our test, we & # x27; ve made thehomemethod useable globally (which cite this module).
SUSA automates exploratory testing with persona-driven behavior, catching bugs that scripted automation misses.
Now let & # x27; s open upios/pages/inner_screen.rband make our second page object.
# filename: pages/inner_screen.rb
faculty Pages
module InnerScreen
class & lt; & lt; self
def has_text (textbook)
postponement {text_exact textbook}
end
end
end
end
module Kernel
def inner_screen
Pages: :InnerScreen
end
end
This is the like structure as our previous page object. In it, we & # x27; re performing an accurate text search.
Let & # x27; s go ahead and update our test to use these page objects.
# filename: ios/spec/navigation_spec.rb
require & # x27; appium_lib & # x27;
require_relative & # x27; .. /pages/home & # x27;
require_relative & # x27; .. /pages/inner_screen & # x27;
describe & # x27; Home Screen Navigation & # x27; do
before (: each) do
appium_txt = File.join (Dir.pwd, & # x27; appium.txt & # x27;)
caps = Appium.load_appium_txt file: appium_txt
Appium: :Driver.new (caps) .start_driver
Appium.promote_appium_methods RSpec: :Core: :ExampleGroup
Appium.promote_singleton_appium_methods Pages
end
after (: each) do
driver_quit
end
it & # x27; First cell & # x27; do
cell_title = home.first_cell.title
home.first_cell.click
inner_screen.has_text cell_title
end
end
We foremost take the page objects (note the use ofrequire_relativeat the top of the file). We so promote the Appium methods to our page objects (e.g.,Appium.promote_singleton_appium_methods Pages). Lastly, we update our test.
Now when we run our test from within theiosdirectory (e.g.,cd ios then rspec) so it will run just the same as it did before.
Now the test is more readable and in best figure. But there is still some refactoring to do to labialise things out. Let & # x27; s draw our test apparatus out of this test file and into a central config that we will be capable to leverage for both iOS and Android.
Central Config
In RSpec, we can configure our test suite from a primal location. This is typically done in a file ringspec_helper.rb. Let & # x27; s create a leaflet calledcommonin the base of our task and add aspec_helper.rbfile to it.
├── Gemfile
├── Gemfile.lock
├── android
│ └── appium.txt
├── common
│ └── spec_helper.rb
└── ios
├── appium.txt
├── pages
│ ├── home.rb
│ └── inner_screen.rb
└── spec
├── navigation_spec.rb
Let & # x27; s open upcommon/spec_helper.rb, add our examination setup to it, and smoothen it up.
# filename: common/spec_helper.rb
require & # x27; rspec & # x27;
require & # x27; appium_lib & # x27;
def setup_driver
return if $ driver
caps = Appium.load_appium_txt file: File.join (Dir.pwd, & # x27; appium.txt & # x27;)
Appium: :Driver.new caps
end
def promote_methods
Appium.promote_singleton_appium_methods Pages
Appium.promote_appium_methods RSpec: :Core: :ExampleGroup
end
setup_drive
promote_methods
RSpec.configure do |config|
config.before (: each) do
$ driver.start_driver
end
config.after (: each) do
driver_quit
end
end
After requiring our requisite libraries, we & # x27; ve created a couple of methods that get executed when the file is loaded. One is to setup (but not part) Appium and another is to promote the method to our page objects and exam. This access is taken to get sure that only one case of Appium is loaded at any one clip.
We then configure our tryout activeness so they run before and after each examination. In them we are commence an Appium session and then cease it.
In order to use this central config, we will need to require it (and remove the unnecessary minute) in our test.
# filename: ios/spec/navigation_spec.rb
require_relative & # x27; .. /pages/home & # x27;
require_relative & # x27; .. /pages/inner_screen & # x27;
require_relative & # x27; .. / .. /common/spec_helper & # x27;
describe & # x27; Home Screen Navigation & # x27; do
it & # x27; First cell & # x27; do
cell_title = home.first_cell.title
home.first_cell.click
inner_screen.has_text cell_title
end
end
Note the order of therequire_relativestatement --they are significant. We need to load our page objects before we can load ourspec_helper, or else the test won & # x27; t run.
If we run the test from within theiosdirectory withrspec, we can see everything execute just like it did before.
Now that we have iOS extend, let & # x27; s wire up an Android test, some page objects, and do certain our test codification to indorse both device.
Including Android
It & # x27; s worth noting that in your real world apps you may be capable to receive a individual set of test and segmented page aim to help make things run seamlessly behind the scenes for both device. And while the behavior in our Android exam app is similar to our iOS test app, it & # x27; s design is different plenty that we & # x27; ll need to create a separate test and page objects.
Let & # x27; s start by creatingspec and pagesfolders within theandroiddirectory and then creating page objects inpages (e.g., home.rb and inner_screen.rb) and a test file inspec (e.g., navigation_spec.rb).
├── Gemfile
├── Gemfile.lock
├── android
│ ├── appium.txt
│ ├── Page
│ │ ├── home.rb
│ │ └── inner_screen.rb
│ └── spec
│ ├── navigation_spec.rb
├── common
│ └── spec_helper.rb
└── ios
├── appium.txt
├── pages
│ ├── home.rb
│ └── inner_screen.rb
└── spec
├── navigation_spec.rb
Now let & # x27; s open and populate our page objects and test file.
faculty Pages
module Home
class & lt; & lt; self
def first_cell
@ found_cell = wait {text 2}
self
end
def click
@ found_cell.click
end
end
end
end
faculty Kernel
def home
Pages: :Home
end
end
This page aim is like to the iOS one except there & # x27; s no rubric search (since we won & # x27; t be needing it).
module Pages
module InnerScreen
class & lt; & lt; self
def has_text (schoolbook)
waiting {find_exact text}
end
end
end
end
module Kernel
def inner_screen
Pages: :InnerScreen
end
end
In this page object we & # x27; re performing a search for an element by textbook (similar to the iOS example), but utilise find_exact instead of text_exact because of how the app is project (we involve to perform a broader hunt that will search across multiple attributes, not exactly the text attribute). Now let & # x27; s telegraph up our test.
require_relative & # x27; .. /pages/home & # x27;
require_relative & # x27; .. /pages/inner_screen & # x27;
require_relative & # x27; .. / .. /common/spec_helper & # x27;
describe & # x27; Home Screen Navigation & # x27; do
it & # x27; First cell & # x27; do
home.first_cell.click
inner_screen.has_text & # x27; Accessibility Node Provider & # x27;
end
end
Now if we cd into the androiddirectory and run our tryout withrspecit should launch the Android aper, load the app, click the first cell, and then end the session. The emulator will remain open, but that & # x27; s something we & # x27; ll address in a future post.
One More Thing
If we use the console with the code that we have flop now, we won & # x27; t be able to cite the page object we & # x27; ve created -- which will be a bit of a pain if we require to cite them when debug test failure. Let & # x27; s fix that.
Let & # x27; s make a new file in ourandroid/spec and ios/specdirectories callrequires.rb. We & # x27; ll move our require statements out of our trial files and into these files instead.
├── Gemfile
├── Gemfile.lock
├── android
│ ├── appium.txt
│ ├── pages
│ │ ├── home.rb
│ │ └── inner_screen.rb
│ └── spec
│ ├── navigation_spec.rb
│ └── requires.rb
├── common
│ └── spec_helper.rb
└── ios
├── appium.txt
├── pages
│ ├── home.rb
│ └── inner_screen.rb
└── spec
├── navigation_spec.rb
└── requires.rb
Here & # x27; s what one of them should look like:
# filename: ios/spec/requires.rb
# require the ios pages
require_relative & # x27; .. /pages/home & # x27;
require_relative & # x27; .. /pages/inner_screen & # x27;
# setup rspec
require_relative & # x27; .. / .. /common/spec_helper & # x27;
Next, we & # x27; ll require to update our tests to use this file.
require_relative & # x27; requires & # x27;
describe & # x27; Home Screen Navigation & # x27; do
it & # x27; First cell & # x27; do
cell_title = home.first_cell.title
home.first_cell.click
inner_screen.has_text cell_title
end
end
# filename: android/spec/navigation_spec.rb
require_relative & # x27; requires & # x27;
describe & # x27; Home Screen Navigation & # x27; do
it & # x27; First cell & # x27; do
home.first_cell.click
inner_screen.has_text & # x27; Accessibility Node Provider & # x27;
end
end
Now that we hold a keyrequires.rbfor each twist, we can narrate the Appium Console to use it. To do that, we & # x27; ll need to add some extra info to ourappium.txt files.
# filename: ios/appium.txt
[caps]
deviceName = & quot; iPhone Simulator & quot;
platformName = & quot; ios & quot;
app = & quot; .. / .. / .. /apps/UICatalog.app.zip & quot;
[appium_lib]
require = [& quot; ./spec/requires.rb & quot;]
# filename: android/appium.txt
[caps]
platformName = & quot; android & quot;
app = & quot; .. / .. / .. /apps/api.apk & quot;
avd = & quot; training & quot;
deviceName = & quot; Android & quot;
[appium_lib]
require = [& quot; ./spec/requires.rb & quot;]
This new requirevalue is solely used by the Appium Console. Now if we runarcfrom either theios or androiddirectory, we & # x27; ll be able to access the page objects just like in our tests.
And if we run our examination from either directory, they will notwithstanding work as point.
Outro
Now that we feature our tests, page objective, and primal configuration all sorted, it & # x27; s time to look at wrapping our test execution and make it so we can run our tryout in the cloud.
About Dave Haeffner: Dave is a recent Appium convert and the author of Elemental Selenium (a free, once weekly Selenium tip newssheet that is read by thousands of screen professionals) as well as The Selenium Guidebook (a step-by-step guide on how to use Selenium Successfully). He is also the creator and sustainer ofChemistryKit(an open-source Selenium framework). He has facilitate numerous companies successfully implement automated acceptance test; including The Motley Fool, ManTech International, Sittercity, and Animoto. He is a founder and co-organizer of theSelenium Hangoutand has spoken at legion conferences and meetups about acceptance examination.
Follow Dave on Twitter -@ tourdedave
Continue the indication the early chapters:
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 FreeTest 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