Appium Workshop
Selenium Conference 2017 - Austin, Texas
@isonic1
justin.ison@gmail.com
Install Windows Dependencies.
Install Mac Dependencies.
Validate everything is installed correctly by running the test example listed these instructions.
Appium is an open-source tool for automating native, mobile web, and hybrid applications on iOS, Windows and Android platforms.
Importantly, Appium is "cross-platform": it allows you to write tests against multiple platforms (iOS, Android, Windows), using the same API. This enables code reuse between iOS, Windows and Android test suites.
Appium is at its heart a webserver that exposes a REST API. It receives connections from a client, listens for commands, executes those commands on a mobile device, and responds with an HTTP response representing the result of the command execution.
Dan Cuellar was the Test Manager at Zoosk in 2011, when he encountered a problem. The length of the test passes on the iOS product was getting out of hand. Less testing was an option, but would come with additional risk, especially with it taking several days to get patches through the iOS App Store Review process. He thought back to his days working on websites and realized automation was the answer.
Dan thought, what if I could get the UIAutomation framework to run in real time like an interpreter? He looked into it and he determined that all he would need to do is find a way to receive, execute, and reply to commands from within a UIAutomation javascript program. Using the utility Apple provided for executing shell commands he was able to cat sequentially ordered text files to receive commands, eval() the output to execute them, and write them back to disk with python. He then prepared code in C# that implemented the Selenium-style syntax to write the sequentially ordered javascript commands. iOSAuto is born.
Selenium Conference 2012: On the second day of the conference, Dan stepped up on stage to give the lightning talk. Jason Huggins, co-creator of Selenium, moderated the lightning talks. Dan experienced technical difficulties getting his presentation to load, and Jason nearly had to move on to the next lightning talk. At the last moment, the screen turned on and Dan jumped into his presentation. He explained the details of his implementation and how it worked, begged for contributors, and in five minutes it was over..
The Mobile Testing Summit: Jason decided that the project should be presented at the Mobile Testing Summit in November, but suggested that the project get a new name first. Many ideas were thrown out and they settled on AppleCart. A day later, while he was perusing some of Apple's guidance on copyright and trademarks, Jason noticed that under the section of examples for names Apple would defend its trademarks against, the first example was "AppleCart". He called Dan and informed him of the situation, and they brainstormed for a bit before Jason hit the jackpot. Appium... Selenium for Apps.
>>>>> Watch the Appium Intro Video <<<<<
Download, clone or pull the latest changes from the appium-workshop repo .
Goto appium-workshop directory and run: bundle install
This workshop is meant to accommodate all skill levels, so we are starting from the very basics. If you are comfortable working ahead of the workshop pace, please do so. However, please DO NOT ask questions on a topic until the workshop is caught up to that point.
I will also try to answer all of your questions. If do not have an answer I will try my best to find it. I’ll follow up with the class later with an email to all unanswered questions.
Please take notes!
Your settings should look like the above.
Close the Android settings.
require 'rubygems'
require 'appium_lib'
capabilities = {
'platformName': 'Android',
'deviceName': 'android',
'app': '/Users/<your name>/appium-workshop/playground/app-debug.apk',
}
server_url = "http://0.0.0.0:4723/wd/hub"
Appium::Driver.new(caps: capabilities).start_driver
Appium.promote_appium_methods Object
find_element(:xpath, "//android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.support.v4.widget.DrawerLayout[1]/android.widget.LinearLayout[1]/android.widget.LinearLayout[1]/android.view.View[1]/android.widget.ImageButton[1]").click
find_element(:xpath, "//android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.support.v4.widget.DrawerLayout[1]/android.widget.RelativeLayout[1]/android.support.v7.widget.RecyclerView[1]/android.widget.FrameLayout[7]").click
find_element(:xpath, "//android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.support.v4.widget.DrawerLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.RelativeLayout[1]/android.widget.Button[2]").click
find_element(:xpath, "//android.widget.FrameLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.LinearLayout[3]/android.widget.LinearLayout[1]/android.widget.Button[1]").click
find_element(:xpath, "//android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.support.v4.widget.DrawerLayout[1]/android.widget.LinearLayout[1]/android.widget.LinearLayout[1]/android.view.View[1]/android.widget.ImageButton[1]").click
find_element(:xpath, "//android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.support.v4.widget.DrawerLayout[1]/android.widget.RelativeLayout[1]/android.support.v7.widget.RecyclerView[1]/android.widget.FrameLayout[1]").click
driver_quit
Analyze the UI components of an android application
require 'rubygems'
require 'appium_lib'
capabilities = {
'platformName': 'Android',
'deviceName': 'android',
'app': '/Users/<your name>/appium-workshop/playground/app-debug.apk',
}
server_url = "http://0.0.0.0:4723/wd/hub"
Appium::Driver.new(caps: capabilities).start_driver
Appium.promote_appium_methods Object
find_element(:id, "ReferenceApp").click
text("Alerts").click
find_element(:id, "com.amazonaws.devicefarm.android.referenceapp:id/notifications_alert_button").click
find_element(:id, "android:id/button1").click
find_element(:id, "ReferenceApp").click
text("Home").click
driver_quit
A Quick Overview
Ruby Specification
require 'appium_lib'
require 'rspec'
describe 'Click Alert Popup' do
before(:each) do
capabilities = {
'platformName': 'Android',
'deviceName': 'android',
'app': '/Users/<your name>/appium-workshop/playground/app-debug.apk'
}
server_url = "http://0.0.0.0:4723/wd/hub"
Appium::Driver.new(caps: capabilities).start_driver
Appium.promote_appium_methods Object
end
it 'Clicks popup and returns to Home view' do
find_element(:id, "ReferenceApp").click
text("Alerts").click
find_element(:id, "com.amazonaws.devicefarm.android.referenceapp:id/notifications_alert_button").click
find_element(:id, "android:id/button1").click
find_element(:id, "ReferenceApp").click
text("Home").click
end
after(:each) do
driver_quit
end
end
require 'appium_lib'
require 'rspec'
describe 'Click Alert Popup' do
before(:each) do
capabilities = {
'platformName': 'Android',
'deviceName': 'android',
'app': '/Users/<your name>/appium-workshop/playground/app-debug.apk'
}
server_url = "http://0.0.0.0:4723/wd/hub"
Appium::Driver.new(caps: capabilities).start_driver
Appium.promote_appium_methods Object
end
it 'Clicks popup and returns to Home view' do
find_element(:id, "ReferenceApp").click
text("Alerts").click
find_element(:id, "com.amazonaws.devicefarm.android.referenceapp:id/notifications_alert_button").click
find_element(:id, "android:id/button1").click
find_element(:id, "ReferenceApp").click
text("Home").click
home_page_text = find_element(:id, "com.amazonaws.devicefarm.android.referenceapp:id/toolbar_title").text
expect(home_page_text).to eq "Homepage"
end
after(:each) do
driver_quit
end
end
require 'appium_lib'
require 'rspec'
describe 'Alerts Page' do
before(:each) do
capabilities = {
'platformName': 'Android',
'deviceName': 'android',
'app': '/Users/<your name>/appium-workshop/playground/app-debug.apk'
}
server_url = "http://0.0.0.0:4723/wd/hub"
Appium::Driver.new(caps: capabilities).start_driver
Appium.promote_appium_methods Object
end
it 'Should Display Popup' do
find_element(:id, "ReferenceApp").click
text("Alerts").click
find_element(:id, "com.amazonaws.devicefarm.android.referenceapp:id/notifications_alert_button").click
expect(find_element(:id, "android:id/alertTitle").text).to eq "Alert Title"
expect(find_element(:id, "android:id/message").text).to eq "This is the alert message"
end
after(:each) do
driver_quit
end
end
require 'appium_lib'
require 'rspec'
describe 'Home Page' do
before(:each) do
capabilities = {
'platformName': 'Android',
'deviceName': 'android',
'app': '/Users/<your name>/appium-workshop/playground/app-debug.apk'
}
server_url = "http://0.0.0.0:4723/wd/hub"
Appium::Driver.new(caps: capabilities).start_driver
Appium.promote_appium_methods Object
end
it 'Should Display Correct Text' do
home_page_text = find_element(:id, "com.amazonaws.devicefarm.android.referenceapp:id/toolbar_title").text
expect(home_page_text).to eq "Homepage"
end
after(:each) do
driver_quit
end
end
Lets run them!
We have some code duplication!
require 'appium_lib'
require 'rspec'
RSpec.configure do |config|
config.color = true
config.tty = true
config.formatter = :documentation
config.before :all do
end
config.before :each do
capabilities = {
'platformName': 'Android',
'deviceName': 'android',
'app': '/Users/<your user>/appium-workshop/playground/app-debug.apk',
}
server_url = "http://0.0.0.0:4723/wd/hub"
Appium::Driver.new(caps: capabilities).start_driver
Appium.promote_appium_methods Object
end
config.after :each do |e|
driver_quit
end
config.after :all do
end
end
require 'spec_helper'
describe 'Home Page' do
it 'Should Display Correct Text' do
home_page_text = find_element(:id, 'com.amazonaws.devicefarm.android.referenceapp:id/toolbar_title').text
expect(home_page_text).to eq "Homepage"
end
end
describe 'Alerts Page' do
it 'Should Display Alert Popup' do
find_element(:id, 'ReferenceApp').click
text("Alerts").click
find_element(:id, 'com.amazonaws.devicefarm.android.referenceapp:id/notifications_alert_button').click
expect(find_element(:id, 'android:id/alertTitle').text).to eq "Alert Title"
expect(find_element(:id, 'android:id/message').text).to eq "This is the alert message"
end
end
[caps]
platformName = "ANDROID"
deviceName = "android"
app = "/Users/<your name>/appium-workshop/android/app-debug.apk"
[appium_lib]
sauce_username = false
sauce_access_key = false
require 'appium_lib'
require 'rspec'
RSpec.configure do |config|
config.color = true
config.tty = true
config.formatter = :documentation
config.before :all do
end
config.before :each do
caps = Appium.load_appium_txt file: 'appium.txt'
Appium::Driver.new(caps).start_driver
Appium.promote_appium_methods Object
end
config.after :each do |e|
driver_quit
end
config.after :all do
end
end
config.after :each do |e|
test = e.description.gsub(" ","_")
screenshot "./output/#{test}.png"
driver_quit
end
This is great information to capture for your tests! Especially since sometimes errors occur that aren't seen in the UI.
So lets add some methods to start capturing the log output at test start and stopping at test end.
Add the methods below (for your specific platform). You should put these below the last end in the spec_helper.rb:
def start_logcat test
pid = spawn("adb logcat -v long", :out=>"./output/logcat-#{test}.log")
ENV["LOGCAT"] = pid.to_s
end
#for mac
def stop_logcat
`kill #{ENV["LOGCAT"]} >> /dev/null 2>&1`
end
#for windows
def stop_logcat
system("taskkill /f /pid #{ENV["LOGCAT"]} > NUL")
end
require 'appium_lib'
require 'rspec'
RSpec.configure do |config|
config.color = true
config.tty = true
config.formatter = :documentation
config.before :all do
end
config.before :each do |e|
caps = Appium.load_appium_txt file: 'appium.txt'
Appium::Driver.new(caps).start_driver
Appium.promote_appium_methods Object
test = e.description.gsub(" ","_")
start_logcat test
end
config.after :each do |e|
test = e.description.gsub(" ","_")
screenshot "./output/#{test}.png"
stop_logcat
driver_quit
end
config.after :all do
end
end
def start_logcat test
pid = spawn("adb logcat -v long", :out=>"./output/logcat-#{test}.log")
ENV["LOGCAT"] = pid.to_s
end
def stop_logcat
`kill #{ENV["LOGCAT"]} >> /dev/null 2>&1`
end
def start_logcat test
pid = spawn("adb logcat -v long", :out=>"./output/logcat-#{test}.log")
ENV["LOGCAT"] = pid.to_s
end
def stop_logcat
`kill #{ENV["LOGCAT"]} >> /dev/null 2>&1`
end
#Add below these methods...
AllureRSpec.configure do |config|
config.include AllureRSpec::Adaptor
config.output_dir = "./output/allure"
config.clean_dir = true
end
require 'appium_lib'
require 'rspec'
require 'pathname'
require 'allure-rspec'
RSpec.configure do |config|
config.color = true ...
... ENV["LOGCAT"] = pid.to_s
end
def stop_logcat
`kill #{ENV["LOGCAT"]} >> /dev/null 2>&1`
end
AllureRSpec.configure do |config|
config.include AllureRSpec::Adaptor
config.output_dir = "./output/allure"
config.clean_dir = true
end
config.after :each do |e|
test = e.description.gsub(" ","_")
screenshot "./output/#{test}.png"
stop_logcat
files = Dir.entries("./output/").grep(/#{test}/)
files.each { |file| e.attach_file("File:", File.new("./output/#{file}")) } unless files.empty?
driver_quit
end
REPL (read–eval–print loop)
require 'spec_helper'
describe 'Crash Page' do
it 'Click Crash Button' do
find_element(:id, 'ReferenceApp').click
text("Crash/Bug").click
find_element(:id, 'com.amazonaws.devicefarm.android.referenceapp:id/crash_button').click
expect(texts.last.text).to eq "Pressing this button will crash the app"
end
end
[caps]
platformName = "ANDROID"
deviceName = "android"
app = "/Users/<your name>/appium-workshop/playground/app-debug.apk"
appPackage = ""
[appium_lib]
wait = 30
sauce_username = false
sauce_access_key = false
Cleanup Time!
require_relative 'locators'
class Common < Locators
TOOLBAR_TITLE = { id: 'com.amazonaws.devicefarm.android.referenceapp:id/toolbar_title' }
HAMBURGER_BUTTON = { id: 'ReferenceApp' }
POPUP_ALERT_TITLE = { id: "android:id/alertTitle" }
POPUP_ALERT_MESSAGE = { id: "android:id/message" }
def click_hamburger
click HAMBURGER_BUTTON
end
def page_title_text
get_text TOOLBAR_TITLE
end
def popup_title
get_text POPUP_ALERT_TITLE
end
def popup_message
get_text POPUP_ALERT_MESSAGE
end
end
require_relative 'common'
class Alert < Common
POPUP_ID = { id: "com.amazonaws.devicefarm.android.referenceapp:id/notifications_alert_button" }
def page_displayed?
wait_true(60) { page_title_text == "Alerts and Dialogs" } #we override the implicit appium_lib wait (30).
end
def click_alert_button
page_displayed?
click POPUP_ID
end
end
require_relative 'common'
class Crash < Common
CRASH_BUTTON_ID = { id: "com.amazonaws.devicefarm.android.referenceapp:id/crash_button" }
CRASH_PAGE_TEXT = { id: "com.amazonaws.devicefarm.android.referenceapp:id/bug_fragment_message" }
def page_displayed?
wait_true(60) { page_title_text == "Crash/Bug Simulator" } #we override the implicit appium_lib wait (30).
end
def click_crash_button
page_displayed?
click CRASH_BUTTON_ID
end
def crash_page_text
get_text CRASH_PAGE_TEXT
end
end
The Programmatic Way!