testautoi 0.9.135 → 0.9.142
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -1
- data/Rakefile +60 -0
- data/bin/calabash-ios +4 -0
- data/bin/calabash-ios-helpers.rb +4 -1
- data/bin/calabash-ios-setup.rb +50 -42
- data/bin/testautoi +19 -7
- data/calabash-cucumber.gemspec +3 -3
- data/doc/calabash-ios-help.txt +19 -7
- data/doc/x-platform-testing.md +250 -0
- data/lib/calabash-cucumber/core.rb +23 -14
- data/lib/calabash-cucumber/ibase.rb +71 -18
- data/lib/calabash-cucumber/launch/simulator_helper.rb +22 -6
- data/lib/calabash-cucumber/launcher.rb +155 -0
- data/lib/calabash-cucumber/operations.rb +1 -1
- data/lib/calabash-cucumber/version.rb +2 -2
- data/scripts/launch.rb +49 -0
- data/staticlib/calabash.framework.zip +0 -0
- metadata +8 -4
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -1,2 +1,62 @@
|
|
1
1
|
require 'bundler'
|
2
|
+
require 'fileutils'
|
3
|
+
|
2
4
|
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
|
7
|
+
task :build_server do
|
8
|
+
|
9
|
+
FRAMEWORK='calabash.framework'
|
10
|
+
ZIP_FILE="#{FRAMEWORK}.zip"
|
11
|
+
|
12
|
+
def build_server
|
13
|
+
return if ENV['SKIP_SERVER']
|
14
|
+
framework_zip = nil
|
15
|
+
dir = ENV['CALABASH_SERVER_PATH'] || File.join('..', '..', 'calabash-ios-server')
|
16
|
+
unless File.exist?(dir)
|
17
|
+
raise <<EOF
|
18
|
+
Unable to find calabash server checked out at #{dir}.
|
19
|
+
Please checkout as #{dir} or set CALABASH_SERVER_PATH to point
|
20
|
+
to Calabash server (branch 0.9.x).
|
21
|
+
EOF
|
22
|
+
end
|
23
|
+
|
24
|
+
FileUtils.cd(dir) do
|
25
|
+
puts 'Building Server'
|
26
|
+
cmd = 'xcodebuild build -project calabash.xcodeproj -target Framework -configuration Debug -sdk iphonesimulator6.1'
|
27
|
+
puts cmd
|
28
|
+
puts `#{cmd}`
|
29
|
+
|
30
|
+
unless File.exist?(FRAMEWORK)
|
31
|
+
raise 'Unable to build framework'
|
32
|
+
end
|
33
|
+
|
34
|
+
puts "Zipping down framework"
|
35
|
+
|
36
|
+
|
37
|
+
zip_cmd = "zip -q -r #{ZIP_FILE} #{FRAMEWORK}"
|
38
|
+
puts zip_cmd
|
39
|
+
puts `#{zip_cmd}`
|
40
|
+
framework_zip = File.expand_path(ZIP_FILE)
|
41
|
+
unless File.exist?(framework_zip)
|
42
|
+
raise 'Unable to zip down framework...'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
FileUtils.mkdir_p('staticlib')
|
49
|
+
output_path = File.join('staticlib', ZIP_FILE)
|
50
|
+
FileUtils.mv(framework_zip,output_path, :force => true)
|
51
|
+
puts "Server built to path #{output_path}"
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
build_server
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
task :build => [:build_server]
|
60
|
+
task :install => [:build_server]
|
61
|
+
task :release => [:build_server]
|
62
|
+
|
data/bin/calabash-ios
CHANGED
@@ -15,6 +15,7 @@ require File.join(File.dirname(__FILE__),"calabash-ios-build")
|
|
15
15
|
@features_dir = File.join(FileUtils.pwd, "features")
|
16
16
|
@source_dir = File.join(File.dirname(__FILE__), '..', 'features-skeleton')
|
17
17
|
@script_dir = File.join(File.dirname(__FILE__), '..', 'scripts')
|
18
|
+
@framework_dir = File.join(File.dirname(__FILE__), '..', 'staticlib')
|
18
19
|
|
19
20
|
if (ARGV.length == 0)
|
20
21
|
print_usage
|
@@ -33,6 +34,9 @@ elsif cmd == 'console'
|
|
33
34
|
elsif cmd == 'build'
|
34
35
|
build
|
35
36
|
exit 0
|
37
|
+
elsif cmd == 'update'
|
38
|
+
update(ARGV)
|
39
|
+
exit 0
|
36
40
|
elsif cmd == 'run'
|
37
41
|
run
|
38
42
|
exit 0
|
data/bin/calabash-ios-helpers.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'tempfile'
|
2
2
|
require 'json'
|
3
3
|
|
4
|
+
UPDATE_TARGETS = ['hooks']
|
4
5
|
|
5
6
|
def msg(title, &block)
|
6
7
|
puts "\n" + "-"*10 + title + "-"*10
|
@@ -21,8 +22,10 @@ def print_usage
|
|
21
22
|
starts an interactive console to interact with your app via Calabash
|
22
23
|
setup [<path>]
|
23
24
|
setup your XCode project for calabash-ios (EXPERIMENTAL)
|
25
|
+
update [target]
|
26
|
+
updates one of the following targets: hooks
|
24
27
|
download
|
25
|
-
|
28
|
+
install latest compatible version of calabash.framework
|
26
29
|
check [{<path to .ipa>|<path to .app>}]
|
27
30
|
check whether an app or ipa is linked with calabash.framework (EXPERIMENTAL)
|
28
31
|
sim locale <lang> [<region>]
|
data/bin/calabash-ios-setup.rb
CHANGED
@@ -94,47 +94,9 @@ def download_calabash(project_path)
|
|
94
94
|
##Download calabash.framework
|
95
95
|
if not File.directory?(File.join(project_path, file))
|
96
96
|
msg("Info") do
|
97
|
-
zip_file = "calabash.framework
|
98
|
-
|
99
|
-
|
100
|
-
require 'uri'
|
101
|
-
|
102
|
-
uri = URI.parse "http://cloud.github.com/downloads/calabash/calabash-ios/#{zip_file}"
|
103
|
-
success = false
|
104
|
-
if has_proxy?
|
105
|
-
proxy_url = proxy
|
106
|
-
connection = Net::HTTP::Proxy(proxy_url[0], proxy_url[1])
|
107
|
-
else
|
108
|
-
connection = Net::HTTP
|
109
|
-
end
|
110
|
-
begin
|
111
|
-
connection.start(uri.host, uri.port) do |http|
|
112
|
-
request = Net::HTTP::Get.new uri.request_uri
|
113
|
-
|
114
|
-
http.request request do |response|
|
115
|
-
if response.code == '200'
|
116
|
-
open zip_file, 'wb' do |io|
|
117
|
-
response.read_body do |chunk|
|
118
|
-
print "."
|
119
|
-
io.write chunk
|
120
|
-
end
|
121
|
-
end
|
122
|
-
success = true
|
123
|
-
else
|
124
|
-
puts "Got bad response code #{response.code}."
|
125
|
-
puts "Aborting..."
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
rescue SocketError => e
|
130
|
-
msg("Error") do
|
131
|
-
puts "Exception: #{e}"
|
132
|
-
puts "Unable to download Calabash. Please check connection."
|
133
|
-
end
|
134
|
-
exit 1
|
135
|
-
end
|
136
|
-
if success
|
137
|
-
puts "\nDownload done: #{file}. Unzipping..."
|
97
|
+
zip_file = File.join(@framework_dir,"calabash.framework.zip")
|
98
|
+
|
99
|
+
if File.exist?(zip_file)
|
138
100
|
if not system("unzip -C -K -o -q -d #{project_path} #{zip_file} -x __MACOSX/* calabash.framework/.DS_Store")
|
139
101
|
msg("Error") do
|
140
102
|
puts "Unable to unzip file: #{zip_file}"
|
@@ -142,8 +104,8 @@ def download_calabash(project_path)
|
|
142
104
|
end
|
143
105
|
exit 1
|
144
106
|
end
|
145
|
-
FileUtils.rm(zip_file)
|
146
107
|
else
|
108
|
+
puts "Inconsistent gem state: Cannot find framework: #{zip_file}"
|
147
109
|
exit 0
|
148
110
|
end
|
149
111
|
end
|
@@ -328,4 +290,50 @@ def validate_app(app)
|
|
328
290
|
end
|
329
291
|
end
|
330
292
|
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
def update(args)
|
297
|
+
if args.length > 0
|
298
|
+
target = args[0]
|
299
|
+
unless UPDATE_TARGETS.include?(target)
|
300
|
+
msg("Error") do
|
301
|
+
puts "Invalid target #{target}. Must be one of: #{UPDATE_TARGETS.join(' ')}"
|
302
|
+
end
|
303
|
+
exit 1
|
304
|
+
end
|
305
|
+
|
306
|
+
|
307
|
+
|
308
|
+
target_file = "features/support/launch.rb"
|
309
|
+
msg("Question") do
|
310
|
+
puts "I'm about to update the #{target_file} file."
|
311
|
+
puts "Please hit return to confirm that's what you want."
|
312
|
+
end
|
313
|
+
exit 2 unless STDIN.gets.chomp == ''
|
314
|
+
|
315
|
+
|
316
|
+
unless File.exist?(target_file)
|
317
|
+
msg("Error") do
|
318
|
+
puts "Unable to find file #{target_file}"
|
319
|
+
puts "Please change directory so that #{target_file} exists."
|
320
|
+
end
|
321
|
+
exit 1
|
322
|
+
end
|
323
|
+
new_launch_script = File.join(@script_dir,"launch.rb")
|
324
|
+
|
325
|
+
FileUtils.cp(new_launch_script, target_file, :verbose => true)
|
326
|
+
|
327
|
+
msg("Info") do
|
328
|
+
puts "File copied.\n"
|
329
|
+
puts "Launch on device using environment variable DEVICE_TARGET=device."
|
330
|
+
end
|
331
|
+
else
|
332
|
+
msg("Error") do
|
333
|
+
puts "update must take one of the following targets: #{UPDATE_TARGETS.join(' ')}"
|
334
|
+
end
|
335
|
+
exit 1
|
336
|
+
|
337
|
+
end
|
338
|
+
|
331
339
|
end
|
data/bin/testautoi
CHANGED
@@ -8,6 +8,8 @@ require 'uri'
|
|
8
8
|
|
9
9
|
require File.join(File.dirname(__FILE__),"calabash-ios-sim")
|
10
10
|
|
11
|
+
@script_dir = File.join(File.dirname(__FILE__), '..', 'scripts')
|
12
|
+
|
11
13
|
@settings_file = File.join(FileUtils.pwd, ".testautoi_settings")
|
12
14
|
|
13
15
|
def print_usage
|
@@ -76,7 +78,7 @@ def print_usage
|
|
76
78
|
Stop recording
|
77
79
|
version
|
78
80
|
prints the gem version
|
79
|
-
|
81
|
+
|
80
82
|
EOF
|
81
83
|
end
|
82
84
|
|
@@ -113,6 +115,8 @@ def run(option)
|
|
113
115
|
env["APP_BUNDLE_PATH"] = File.join(FileUtils.pwd, app_bundle)
|
114
116
|
if device_udid == ''
|
115
117
|
# Use a Simulator
|
118
|
+
#Calabash::Cucumber::SimulatorHelper.relaunch(env["APP_BUNDLE_PATH"], @settings["sim_version"], @settings["sim_device"])
|
119
|
+
#calabash_sim_location(['on', app_bundle_id])
|
116
120
|
if option == 'console'
|
117
121
|
Calabash::Cucumber::SimulatorHelper.relaunch(env["APP_BUNDLE_PATH"], @settings["sim_version"], @settings["sim_device"])
|
118
122
|
else
|
@@ -122,9 +126,11 @@ def run(option)
|
|
122
126
|
else
|
123
127
|
env["NO_LAUNCH"] = "1"
|
124
128
|
get_device_ip
|
125
|
-
if ENV["
|
129
|
+
if ENV["CLEAN_INSTALL"] == "1"
|
126
130
|
uninstall_app(app_bundle_id)
|
127
131
|
install_app(File.join(FileUtils.pwd, app_bundle))
|
132
|
+
elsif ENV["NO_INSTALL"] != "1"
|
133
|
+
install_app(File.join(FileUtils.pwd, app_bundle))
|
128
134
|
end
|
129
135
|
launch_app(app_url)
|
130
136
|
sleep(5)
|
@@ -426,6 +432,14 @@ def uninstall_app(app_bundle_id)
|
|
426
432
|
raise "Failed to install the app" if result == false
|
427
433
|
end
|
428
434
|
|
435
|
+
def tracetemplate
|
436
|
+
"/Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/PlugIns/AutomationInstrument.bundle/Contents/Resources/Automation.tracetemplate"
|
437
|
+
end
|
438
|
+
|
439
|
+
def simulator_path
|
440
|
+
"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Applications/iPhone\\ Simulator.app/Contents/MacOS/iPhone\\ Simulator"
|
441
|
+
end
|
442
|
+
|
429
443
|
def launch_app(app_url)
|
430
444
|
udid = @settings["device_udid"].to_s
|
431
445
|
udids = Device.detect
|
@@ -435,13 +449,12 @@ def launch_app(app_url)
|
|
435
449
|
udid = udids.first
|
436
450
|
end
|
437
451
|
raise "The device #{udid} is not found." if not udids.include?(udid)
|
438
|
-
tracetempl = "/Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/PlugIns/AutomationInstrument.bundle/Contents/Resources/Automation.tracetemplate"
|
439
452
|
launcher = "./AppLaunch.app"
|
440
453
|
script = 'LaunchApp.js'
|
441
454
|
text = File.read(File.join(File.dirname(__FILE__), script))
|
442
455
|
File.open(File.join(FileUtils.pwd, script), "w") {|file| file.puts text.gsub(/\[%app%\]/, app_url)}
|
443
456
|
script = File.join(FileUtils.pwd, script)
|
444
|
-
cmd = `instruments -w #{udid} -t #{
|
457
|
+
cmd = `instruments -w #{udid} -t #{tracetemplate} #{launcher_path} -e UIASCRIPT #{script}`
|
445
458
|
end
|
446
459
|
|
447
460
|
def get_device_ip
|
@@ -454,20 +467,19 @@ def get_device_ip
|
|
454
467
|
udid = udids.first
|
455
468
|
end
|
456
469
|
raise "The device #{udid} is not found." if not udids.include?(udid)
|
457
|
-
tracetempl = "/Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/PlugIns/AutomationInstrument.bundle/Contents/Resources/Automation.tracetemplate"
|
458
470
|
launcher = "./AppLaunch.app"
|
459
471
|
script = 'GetIPAddress.js'
|
460
472
|
text = File.read(File.join(File.dirname(__FILE__), script))
|
461
473
|
File.open(File.join(FileUtils.pwd, script), "w") {|file| file.puts text}
|
462
474
|
script = File.join(FileUtils.pwd, script)
|
463
475
|
File.open(File.join(FileUtils.pwd, "Device.cfg"), "w") {|file| file.puts "IPAddress="}
|
464
|
-
cmd = `instruments -w #{udid} -t #{
|
476
|
+
cmd = `instruments -w #{udid} -t #{tracetemplate} #{launcher_path} -e UIASCRIPT #{script}`
|
465
477
|
puts File.read(File.join(FileUtils.pwd, "Device.cfg"))
|
466
478
|
end
|
467
479
|
|
468
480
|
def start_sim
|
469
481
|
quit_sim
|
470
|
-
pid = spawn(
|
482
|
+
pid = spawn(simulator_path)
|
471
483
|
end
|
472
484
|
|
473
485
|
def reset_sim
|
data/calabash-cucumber.gemspec
CHANGED
@@ -11,9 +11,9 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.homepage = "http://calaba.sh"
|
12
12
|
s.summary = %q{Client for calabash-ios-server for automated functional testing on iOS}
|
13
13
|
s.description = %q{calabash-cucumber drives tests for native iOS apps. You must link your app with calabash-ios-server framework to execute tests.}
|
14
|
-
s.files = `git ls-files`.split("\n")
|
14
|
+
s.files = `git ls-files`.split("\n").concat(["staticlib/calabash.framework.zip"])
|
15
15
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
-
s.executables =
|
16
|
+
s.executables = "calabash-ios"
|
17
17
|
s.require_paths = ["lib"]
|
18
18
|
|
19
19
|
s.add_dependency( "cucumber" )
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
s.add_dependency( "location-one", "~>0.0.9")
|
25
25
|
s.add_dependency( "httpclient","2.3.2")
|
26
26
|
s.add_dependency( "bundler", "~> 1.1")
|
27
|
-
s.add_dependency( "run_loop", "0.0.
|
27
|
+
s.add_dependency( "run_loop", "0.0.7" )
|
28
28
|
s.add_dependency( "awesome_print")
|
29
29
|
|
30
30
|
end
|
data/doc/calabash-ios-help.txt
CHANGED
@@ -8,7 +8,10 @@ Usage: calabash-ios <command-name> [parameters]
|
|
8
8
|
sim locale [lang] [regional]?
|
9
9
|
sim reset
|
10
10
|
sim acc
|
11
|
-
sim device
|
11
|
+
sim device {iPad,iPad_Retina,iPhone,iPhone_Retina,iPhone_Retina_4inch}
|
12
|
+
sim location {on|off} <bundleid>
|
13
|
+
|
14
|
+
|
12
15
|
|
13
16
|
Commands:
|
14
17
|
gen creates a skeleton features dir. This is usually used once when
|
@@ -16,8 +19,8 @@ Usage: calabash-ios <command-name> [parameters]
|
|
16
19
|
the right step definitions and environment to run with cucumber.
|
17
20
|
console
|
18
21
|
starts an interactive console to interact with your app via Calabash.
|
19
|
-
|
20
|
-
|
22
|
+
Supports setting environment var CALABASH_IRBRC for custom .irbrc file.
|
23
|
+
|
21
24
|
setup [path]? (EXPERIMENTAL) Automates setting up your iOS Xcode project
|
22
25
|
with calabash-ios-server. It is your responsibility to ensure
|
23
26
|
that your production build does not link with calabash.framework.
|
@@ -41,18 +44,24 @@ Usage: calabash-ios <command-name> [parameters]
|
|
41
44
|
If something goes wrong. Close Xcode and copy project.pbxproj.bak
|
42
45
|
to project.pbxproj inside your .xcodeproj folder.
|
43
46
|
|
47
|
+
update [target]
|
48
|
+
updates one of the following targets: hooks
|
49
|
+
|
44
50
|
download [opt_path]?
|
45
|
-
|
51
|
+
copies current compatible version of calabash.framework to your project.
|
46
52
|
It should be run from a directory containing an Xcode project,
|
47
53
|
or optionally opt_path should be supplied and pointing to a
|
48
54
|
directory containing an Xcode project.
|
49
|
-
|
55
|
+
Will copy in the latest version that matches the
|
50
56
|
currently installed calabash-cucumber gem.
|
51
57
|
To update Calabash for your project run
|
52
58
|
|
53
|
-
gem
|
59
|
+
gem install calabash-cucumber
|
54
60
|
calabash-ios download
|
55
61
|
|
62
|
+
Then clean and rebuild to your project.
|
63
|
+
Check the current server version on http://localhost:37265/version
|
64
|
+
|
56
65
|
check (EXPERIMENTAL) [.app or .ipa]?
|
57
66
|
check whether an app or ipa is linked with calabash.framework
|
58
67
|
if called without parameter [.app or .ipa] then pwd should be
|
@@ -66,8 +75,11 @@ Usage: calabash-ios <command-name> [parameters]
|
|
66
75
|
for the optional regional parameter, for example,
|
67
76
|
da_DK, en_US.
|
68
77
|
|
78
|
+
sim location {on|off} <bundleid>
|
79
|
+
set allow location on/off for current project or bundleid
|
80
|
+
|
69
81
|
sim reset (EXPERIMENTAL) Will select "Reset Content and Settings"
|
70
82
|
in the iOS Simulators using AppleScript.
|
71
83
|
|
72
84
|
sim device [device] Will set the default iOS Simulator device.
|
73
|
-
[device] can be one of iPad
|
85
|
+
[device] can be one of iPad iPad_Retina iPhone iPhone_Retina iPhone_Retina_4inch
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# Cross-platform Acceptance Testing Best Practices
|
2
|
+
|
3
|
+
Test automation is programming - hence, well-established practices of programming apply to test automation. Ruby is object-oriented, and most Calabash tests should also follow good object-oriented design (e.g., principles of abstraction, separation of concerns, modularity, reuse...).
|
4
|
+
|
5
|
+
A well-established pattern in test engineering is the *Page-Object Pattern* (POP). While originating in web testing, the ideas of POP apply equally well to native mobile. In this short article, we'll illustrate how to use the page object pattern to better architect test code and obtain better cross-platform code reuse.
|
6
|
+
|
7
|
+
We've created a sample project: [X-Platform-Example](https://github.com/calabash/x-platform-example) using the open source WordPress app. If you want to follow along and try things out, go to the project page [https://github.com/calabash/x-platform-example](https://github.com/calabash/x-platform-example) and follow install and run instructions. You can also choose to just read about the principles in this article and try to implement them in you own app.
|
8
|
+
|
9
|
+
LessPainful also provides both on-site and public training courses where we teach the use of Calabash as well as cross-platform and automated testing best practices. If you're interested in a two-day, hands-on course, please contact us at [contact@lesspainful.com](mailto:contact@lesspainful.com).
|
10
|
+
|
11
|
+
# Page Objects
|
12
|
+
|
13
|
+
A *page object* is an object that represents a single screen (page) in your application. For mobile, "screen object" would possibly be a better word, but Page Object is an established term that we'll stick with.
|
14
|
+
|
15
|
+
A page object should abstract a single screen in your application. It should expose methods to query the state and data of the screen as well as methods to take actions on the screen.
|
16
|
+
|
17
|
+
As a trivial example, a "login screen" consisting of username and password text fields and a "Login" button could expose a method `login(user,pass)` method that would abstract the details of entering username, password, touching the "Login" button, as well as 'transitioning' to another page (after login). A screen with a list of talks for a conference could expose a `talks()` method to return the visible talks and perhaps a `details(talk)` method to navigate to details for a particular talk.
|
18
|
+
|
19
|
+
The most obvious benefit of this is abstraction and reuse. If you have several steps needing to navigate to details, the code for `details(talk)` is reused. Also, callers of this method need not worry about the details (e.g. query and touch) or navigating to this screen.
|
20
|
+
|
21
|
+
# Cross-platform Core Idea
|
22
|
+
|
23
|
+
|
24
|
+
Let's go into more detail with this last example. Consider the following sketch of a class (don't do it exactly like this - read on a bit):
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
|
28
|
+
class TalksScreen
|
29
|
+
def talks
|
30
|
+
# query all talks...
|
31
|
+
end
|
32
|
+
|
33
|
+
def details(talk)
|
34
|
+
#touch talk…
|
35
|
+
end
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
|
40
|
+
Suppose you're building the same app for iPhone and Android phones. Most likely the interface of the `TalksScreen` class makes complete sense on both platforms. This means that the calling code, which is usually in a step definition, is independent of platform - hence it can be reused across platforms.
|
41
|
+
|
42
|
+
Working this way gets you complete reuse of Cucumber features as well as step definitions: the details of interacting with the screen is pushed to page object implementations.
|
43
|
+
|
44
|
+
The idea is that you provide an implementation of page objects for each platform you need to support (e.g. iPhone, iPad, Android phone, Android tablet, mobile web, desktop web,…).
|
45
|
+
|
46
|
+
# Cross-platform in practice
|
47
|
+
|
48
|
+
So… The idea and design looks good. The question now is how to implement this is practice. Here we describe the mechanics and below you'll find an example extracted from [X-Platform-Example](https://github.com/calabash/x-platform-example).
|
49
|
+
|
50
|
+
There are a couple of ingredients we need.
|
51
|
+
|
52
|
+
1. For each screen you want to automate, decide on an interface for a page object class (e.g. like `TalksScreen` above).
|
53
|
+
2. Use only custom steps, and in each step definition *only* use page objects and their methods (no direct calls to Calabash iOS or Calabash Android APIs).
|
54
|
+
3. For each supported platform, define a class with implementations of the page-object methods.
|
55
|
+
4. Create a Cucumber profile (`config/cucumber.yml`). Define a profile for each platform (e.g. `android` and `ios`), and ensure that the profile *only* loads the page object classes for the platform.
|
56
|
+
5. Rejoice and profit!
|
57
|
+
|
58
|
+
Let's see what these steps look like in a concrete example on the [X-Platform-Example](https://github.com/calabash/x-platform-example).
|
59
|
+
|
60
|
+
# Example
|
61
|
+
|
62
|
+
## Step 1 - Interface
|
63
|
+
For the wordpress app, let's focus on the Login/Add-WordPress Blog screen. This method has a single method: `login(user)` which takes a hash `{:email => 'username', :password => 'somepass'}` representing a user:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
|
67
|
+
def login(user)
|
68
|
+
#…
|
69
|
+
end
|
70
|
+
|
71
|
+
def assert_invalid_login_message()
|
72
|
+
#…
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
For this simple screen the interface consists of just these two methods.
|
77
|
+
|
78
|
+
## Step 2 - Step definitions
|
79
|
+
|
80
|
+
We have a feature alà
|
81
|
+
|
82
|
+
Scenario: Invalid login to WordPress.com blog
|
83
|
+
Given I am about to login
|
84
|
+
When I enter invalid credentials
|
85
|
+
Then I am presented with an error message to correct credentials
|
86
|
+
|
87
|
+
Below are step definitions that *only use the page objects* and no Calabash methods like touch, query…
|
88
|
+
|
89
|
+
For the following, assume we have also a Page Object class `WelcomePage` with a method `wordpress_blog` that transitions to the `AddWordPressBlog` screen:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
|
93
|
+
## Invalid login ##
|
94
|
+
Given /^I am about to login$/ do
|
95
|
+
|
96
|
+
welcome = page(WelcomePage).await
|
97
|
+
@page = welcome.wordpress_blog
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
When /^I enter invalid credentials$/ do
|
102
|
+
@page = @page.login(USERS[:invalid])
|
103
|
+
end
|
104
|
+
|
105
|
+
Then /^I am presented with an error message to correct credentials$/ do
|
106
|
+
@page.assert_invalid_login_message
|
107
|
+
screenshot_embed
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
The `page` method is a helper method in Calabash which initializes a page object from a class. The `await` method just returns the page object after waiting for the page to be loaded.
|
112
|
+
|
113
|
+
We store the page object in an instance variable in the cucumber world (`@page`) and use it in the subsequent steps.
|
114
|
+
|
115
|
+
Notice how the steps only use page-object methods. This feature, as well as the step definitions, can be 100% reused across platforms. Great!
|
116
|
+
|
117
|
+
## Step 3 - Platform implementations
|
118
|
+
|
119
|
+
Now we need to give an implementation of the `WordPressComPage` on iOS and Android (and all other supported platforms). We put those implementations in separte directories `features/ios/pages` and `features/android/pages` and name them `word_press_com_page_ios.rb` and `word_press_com_android.rb`.
|
120
|
+
|
121
|
+
Here is the implementation for iOS. It uses an abstract Page Object Class defined in Calabash iOS (`Calabash::IBase`).
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
require 'calabash-cucumber/ibase'
|
125
|
+
|
126
|
+
class WordPressComPage < Calabash::IBase
|
127
|
+
|
128
|
+
def title
|
129
|
+
"Sign In"
|
130
|
+
end
|
131
|
+
|
132
|
+
def login(user)
|
133
|
+
touch("view marked:'Username'")
|
134
|
+
await_keyboard
|
135
|
+
|
136
|
+
keyboard_enter_text user[:email]
|
137
|
+
|
138
|
+
touch("view marked:'Password'")
|
139
|
+
|
140
|
+
keyboard_enter_text user[:password]
|
141
|
+
done
|
142
|
+
|
143
|
+
wait_for_elements_do_not_exist(["tableViewCell activityIndicatorView"],
|
144
|
+
:timeout => 120)
|
145
|
+
|
146
|
+
|
147
|
+
if element_exists(invalid_login_query)
|
148
|
+
self
|
149
|
+
else
|
150
|
+
page(MainPage).await
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def assert_invalid_login_message
|
155
|
+
check_element_exists(trait)
|
156
|
+
check_element_exists(invalid_login_query)
|
157
|
+
end
|
158
|
+
|
159
|
+
def invalid_login_query
|
160
|
+
"label {text LIKE '*Sign in failed*'}"
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
```
|
166
|
+
|
167
|
+
And for Android:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
class WordPressComPage < Calabash::ABase
|
171
|
+
|
172
|
+
def trait
|
173
|
+
"* id:'username'"
|
174
|
+
end
|
175
|
+
|
176
|
+
def await(opts={})
|
177
|
+
wait_for_elements_exist([trait])
|
178
|
+
self
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
def login(user)
|
183
|
+
query("* id:'username'",{:setText => user[:email]})
|
184
|
+
query("* id:'password'",{:setText => user[:password]})
|
185
|
+
|
186
|
+
performAction('scroll_down')
|
187
|
+
|
188
|
+
touch(login_button_query)
|
189
|
+
|
190
|
+
sleep(1)#Chance to show Dialog
|
191
|
+
|
192
|
+
wait_for(:timeout => 60, :timeout_message => "Timed out logging in") do
|
193
|
+
current_dialogs = query("DialogTitle",:text)
|
194
|
+
|
195
|
+
empty_dialog = current_dialogs.empty?
|
196
|
+
error_dialog = current_dialogs.include?("Error")
|
197
|
+
no_network_dialog = current_dialogs.include?("No network available")
|
198
|
+
|
199
|
+
empty_dialog or error_dialog or no_network_dialog
|
200
|
+
end
|
201
|
+
|
202
|
+
main_page = page(MainPage)
|
203
|
+
|
204
|
+
if main_page.current_page?
|
205
|
+
main_page.await
|
206
|
+
else
|
207
|
+
self
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def invalid_login_query
|
212
|
+
login_button_query
|
213
|
+
end
|
214
|
+
|
215
|
+
def login_button_query
|
216
|
+
"android.widget.Button marked:'Log In'"
|
217
|
+
end
|
218
|
+
|
219
|
+
def assert_invalid_login_message
|
220
|
+
check_element_exists(invalid_login_query)
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
227
|
+
## Step 4 - Conditional loading
|
228
|
+
|
229
|
+
The final missing part is conditionally loading page-object implementations based on which platform we're running. This is done using Cucumber *profiles*. We create a file `config/cucumber.yml`
|
230
|
+
|
231
|
+
---
|
232
|
+
android: RESET_BETWEEN_SCENARIOS=1 PLATFORM=android -r features/support -r features/android/support -r features/android/helpers -r features/step_definitions -r features/android/pages/
|
233
|
+
|
234
|
+
ios: APP_BUNDLE_PATH=ios-source/3.3.1/build/Applications/WordPress.app RESET_BETWEEN_SCENARIOS=1 PLATFORM=ios -r features/support -r features/ios/support -r features/ios/helpers -r features/step_definitions -r features/ios/pages
|
235
|
+
|
236
|
+
We're using Cucumbers `-r` option to only load a subset of Ruby files. We can then execute the tests as specified [here](https://github.com/calabash/x-platform-example).
|
237
|
+
|
238
|
+
iOS:
|
239
|
+
|
240
|
+
cucumber -p ios features/login.feature
|
241
|
+
|
242
|
+
Android:
|
243
|
+
|
244
|
+
calabash-android run path_to.apk -p android features/login.feature
|
245
|
+
|
246
|
+
## Conclusion
|
247
|
+
|
248
|
+
We've described how to improve the architecture of your test code base: using page objects you get better abstraction, reuse and cross-platform comes more easily. We've created an open source sample project that you can use for inspiration: [X-Platform-Example](https://github.com/calabash/x-platform-example).
|
249
|
+
|
250
|
+
Comments and improvements welcome!
|
@@ -325,7 +325,12 @@ module Calabash
|
|
325
325
|
device = options["DEVICE"] || ENV["DEVICE"] || "iphone"
|
326
326
|
|
327
327
|
unless os
|
328
|
-
|
328
|
+
if @calabash_launcher && @calabash_launcher.active?
|
329
|
+
major = @calabash_launcher.ios_major_version
|
330
|
+
else
|
331
|
+
major = Calabash::Cucumber::SimulatorHelper.ios_major_version
|
332
|
+
end
|
333
|
+
|
329
334
|
unless major
|
330
335
|
raise <<EOF
|
331
336
|
Unable to determine iOS major version
|
@@ -471,24 +476,28 @@ EOF
|
|
471
476
|
end
|
472
477
|
|
473
478
|
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
479
|
+
## args :app for device bundle id, for sim path to app
|
480
|
+
##
|
481
|
+
def start_test_server_in_background(args={})
|
482
|
+
target = args[:device_target] || :simulator
|
483
|
+
stop_test_server
|
484
|
+
@calabash_launcher = Calabash::Cucumber::Launcher.new(target)
|
485
|
+
@calabash_launcher.relaunch(args)
|
480
486
|
|
481
|
-
@ios_device = RunLoop.run(:app => app_bundle_path)
|
482
487
|
end
|
483
488
|
|
484
|
-
def
|
485
|
-
|
489
|
+
def stop_test_server
|
490
|
+
if @calabash_launcher
|
491
|
+
@calabash_launcher.stop
|
492
|
+
end
|
486
493
|
end
|
487
494
|
|
488
|
-
def
|
489
|
-
|
490
|
-
|
491
|
-
|
495
|
+
def send_uia_command(opts ={})
|
496
|
+
run_loop = opts[:run_loop] || (@calabash_launcher && @calabash_launcher.active? && @calabash_launcher.run_loop)
|
497
|
+
command = opts[:command]
|
498
|
+
raise ArgumentError, 'please supply :run_loop or instance var @calabash_launcher' unless run_loop
|
499
|
+
raise ArgumentError, 'please supply :command' unless command
|
500
|
+
RunLoop.send_command(run_loop, opts[:command])
|
492
501
|
end
|
493
502
|
|
494
503
|
|
@@ -4,42 +4,95 @@ require 'calabash-cucumber/operations'
|
|
4
4
|
class Calabash::IBase
|
5
5
|
include Calabash::Cucumber::Operations
|
6
6
|
|
7
|
-
|
8
|
-
@world = world
|
9
|
-
end
|
10
|
-
|
11
|
-
def embed(*args)
|
12
|
-
@world.send(:embed,*args)
|
13
|
-
end
|
7
|
+
attr_accessor :world, :transition_duration
|
14
8
|
|
15
|
-
def
|
16
|
-
|
9
|
+
def initialize(world, transition_duration=0.5)
|
10
|
+
self.world = world
|
11
|
+
self.transition_duration = transition_duration
|
17
12
|
end
|
18
13
|
|
19
14
|
def trait
|
15
|
+
raise "You should define a trait method or a title method" unless respond_to?(:title)
|
20
16
|
"navigationItemView marked:'#{self.title}'"
|
21
17
|
end
|
22
18
|
|
23
|
-
def
|
24
|
-
|
19
|
+
def current_page?
|
20
|
+
element_exists(trait)
|
25
21
|
end
|
26
22
|
|
27
|
-
def
|
23
|
+
def page(clz, *args)
|
24
|
+
clz.new(world, *args)
|
25
|
+
end
|
28
26
|
|
27
|
+
def await(wait_opts={})
|
28
|
+
wait_for_elements_exist([trait], wait_opts)
|
29
|
+
unless wait_opts.has_key?(:await_animation) && !wait_opts[:await_animation]
|
30
|
+
sleep(transition_duration)
|
31
|
+
end
|
32
|
+
self
|
29
33
|
end
|
30
34
|
|
31
|
-
|
35
|
+
##
|
36
|
+
# Performs a transition from receiver page to another by performing a +:tap+ gesture
|
37
|
+
# or a user specified +:action+.
|
38
|
+
# Caller must supply a hash of options +transition_options+ to describe the transition.
|
39
|
+
# Transition options may have the following keys
|
40
|
+
#
|
41
|
+
# +:tap+: A uiquery used to perform a tap gesture to begin transition
|
42
|
+
# +:action+: A proc to use begin transition (either :tap or :action must be supplied)
|
43
|
+
# +:page+: A page object or page object class to transition to (target page). If a class is provided this
|
44
|
+
# is instantiated using the +page+ method of self. If no +:page+ is supplied, +self+ is used.
|
45
|
+
# +:await+: If specified and truthy will await the +:page+ after performing gesture (usually to wait
|
46
|
+
# for animation to finish)
|
47
|
+
# +:tap_options+: If +:tap+ is provided used to pass as options to touch
|
48
|
+
# +:wait_options+: When awaiting target page, pass these options to the +await+ method
|
49
|
+
#
|
50
|
+
# Returns the transition target page
|
51
|
+
#
|
52
|
+
# Note it is assumed that the target page is a Calabash::IBase (or acts accordingly)
|
53
|
+
def transition(transition_options={})
|
54
|
+
uiquery = transition_options[:tap]
|
55
|
+
action = transition_options[:action]
|
56
|
+
page_arg = transition_options[:page]
|
57
|
+
should_await = transition_options.has_key?(:await) ? transition_options[:await] : true
|
32
58
|
|
33
|
-
|
59
|
+
if action.nil? && uiquery.nil?
|
60
|
+
raise "Called transition without providing a gesture (:tap or :action) #{transition_options}"
|
61
|
+
end
|
34
62
|
|
35
|
-
|
36
|
-
|
37
|
-
|
63
|
+
if uiquery
|
64
|
+
tap_options = transition_options[:tap_options] || {}
|
65
|
+
touch(uiquery, tap_options)
|
66
|
+
else
|
67
|
+
action.call()
|
68
|
+
end
|
69
|
+
|
70
|
+
page_obj = page_arg.is_a?(Class) ? page(page_arg) : page_arg
|
71
|
+
page_obj ||= self
|
72
|
+
|
73
|
+
if should_await
|
74
|
+
wait_opts = transition_options[:wait_options] || {}
|
75
|
+
if page_obj == self
|
76
|
+
unless wait_opts.has_key?(:await_animation) && !wait_opts[:await_animation]
|
77
|
+
sleep(transition_duration)
|
78
|
+
end
|
79
|
+
else
|
80
|
+
page_obj.await(wait_opts)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
page_obj
|
38
85
|
end
|
39
86
|
|
40
|
-
def await_screenshot(wait_opts={},screenshot_opts={})
|
87
|
+
def await_screenshot(wait_opts={}, screenshot_opts={})
|
41
88
|
await(wait_opts)
|
42
89
|
screenshot_embed(screenshot_opts)
|
43
90
|
end
|
44
91
|
|
92
|
+
|
93
|
+
protected
|
94
|
+
def method_missing(name, *args, &block)
|
95
|
+
world.send(name, *args, &block)
|
96
|
+
end
|
97
|
+
|
45
98
|
end
|
@@ -19,6 +19,10 @@ module Calabash
|
|
19
19
|
|
20
20
|
DEFAULT_SIM_RETRY = 2
|
21
21
|
|
22
|
+
# Load environment variable for showing full console output
|
23
|
+
# If not env var set then we use true; i.e. output to console in full
|
24
|
+
FULL_CONSOLE_OUTPUT = ENV['CALABASH_FULL_CONSOLE_OUTPUT'] == 'false' ? false : true
|
25
|
+
|
22
26
|
def self.relaunch(path, sdk = nil, version = 'iphone', args = nil)
|
23
27
|
|
24
28
|
app_bundle_path = app_bundle_or_raise(path)
|
@@ -176,12 +180,18 @@ module Calabash
|
|
176
180
|
timeout = (ENV['CONNECT_TIMEOUT'] || DEFAULT_SIM_WAIT).to_i
|
177
181
|
retry_count = 0
|
178
182
|
connected = false
|
179
|
-
|
180
|
-
|
183
|
+
|
184
|
+
if FULL_CONSOLE_OUTPUT
|
185
|
+
puts "Waiting at most #{timeout} seconds for simulator (CONNECT_TIMEOUT)"
|
186
|
+
puts "Retrying at most #{max_retry_count} times (MAX_CONNECT_RETRY)"
|
187
|
+
end
|
188
|
+
|
181
189
|
until connected do
|
182
190
|
raise "MAX_RETRIES" if retry_count == max_retry_count
|
183
191
|
retry_count += 1
|
184
|
-
|
192
|
+
if FULL_CONSOLE_OUTPUT
|
193
|
+
puts "(#{retry_count}.) Start Simulator #{sdk}, #{version}, for #{app_bundle_path}"
|
194
|
+
end
|
185
195
|
begin
|
186
196
|
Timeout::timeout(timeout, TimeoutErr) do
|
187
197
|
simulator = launch(app_bundle_path, sdk, version, args)
|
@@ -196,7 +206,9 @@ module Calabash
|
|
196
206
|
if connected
|
197
207
|
server_version = get_version
|
198
208
|
if server_version
|
199
|
-
|
209
|
+
if FULL_CONSOLE_OUTPUT
|
210
|
+
p server_version
|
211
|
+
end
|
200
212
|
unless version_check(server_version)
|
201
213
|
msgs = ["You're running an older version of Calabash server with a newer client",
|
202
214
|
"Client:#{Calabash::Cucumber::VERSION}",
|
@@ -241,7 +253,9 @@ module Calabash
|
|
241
253
|
|
242
254
|
def self.ping_app
|
243
255
|
url = URI.parse(ENV['DEVICE_ENDPOINT']|| "http://localhost:37265/")
|
244
|
-
|
256
|
+
if FULL_CONSOLE_OUTPUT
|
257
|
+
puts "Ping #{url}..."
|
258
|
+
end
|
245
259
|
http = Net::HTTP.new(url.host, url.port)
|
246
260
|
res = http.start do |sess|
|
247
261
|
sess.request Net::HTTP::Get.new url.path
|
@@ -260,7 +274,9 @@ module Calabash
|
|
260
274
|
endpoint += "/" unless endpoint.end_with? "/"
|
261
275
|
url = URI.parse("#{endpoint}version")
|
262
276
|
|
263
|
-
|
277
|
+
if FULL_CONSOLE_OUTPUT
|
278
|
+
puts "Fetch version #{url}..."
|
279
|
+
end
|
264
280
|
begin
|
265
281
|
body = Net::HTTP.get_response(url).body
|
266
282
|
res = JSON.parse(body)
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'calabash-cucumber/launch/simulator_helper'
|
2
|
+
require 'sim_launcher'
|
3
|
+
require 'run_loop'
|
4
|
+
|
5
|
+
|
6
|
+
class Calabash::Cucumber::Launcher
|
7
|
+
attr_accessor :run_loop
|
8
|
+
attr_accessor :device_target
|
9
|
+
attr_accessor :ios_version
|
10
|
+
|
11
|
+
def initialize(device_target=:simulator)
|
12
|
+
self.device_target = device_target
|
13
|
+
end
|
14
|
+
|
15
|
+
class CalabashLauncherTimeoutErr < Timeout::Error
|
16
|
+
end
|
17
|
+
|
18
|
+
def calabash_no_stop?
|
19
|
+
ENV['NO_LAUNCH']=="1" or ENV['NO_STOP']=="1"
|
20
|
+
end
|
21
|
+
|
22
|
+
def device_target?
|
23
|
+
ENV['DEVICE_TARGET'] == 'device'
|
24
|
+
end
|
25
|
+
|
26
|
+
def simulator_target?
|
27
|
+
ENV['DEVICE_TARGET'] == 'simulator'
|
28
|
+
end
|
29
|
+
|
30
|
+
def active?
|
31
|
+
(simulator_target? || device_target?) && (not run_loop.nil?)
|
32
|
+
end
|
33
|
+
|
34
|
+
def ios_major_version
|
35
|
+
v = ios_version
|
36
|
+
(v && v.split('.')[0])
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def reset_app_jail(sdk, app_path)
|
41
|
+
return if device_target?
|
42
|
+
|
43
|
+
app = File.basename(app_path)
|
44
|
+
bundle = `find "#{ENV['HOME']}/Library/Application Support/iPhone Simulator/#{sdk}/Applications/" -type d -depth 2 -name "#{app}" | head -n 1`
|
45
|
+
return if bundle.empty? # Assuming we're already clean
|
46
|
+
|
47
|
+
sandbox = File.dirname(bundle)
|
48
|
+
['Library', 'Documents', 'tmp'].each do |dir|
|
49
|
+
FileUtils.rm_rf(File.join(sandbox, dir))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def relaunch(args={})
|
54
|
+
RunLoop.stop(run_loop) if run_loop
|
55
|
+
|
56
|
+
if device_target?
|
57
|
+
default_args = {:app => ENV['BUNDLE_ID']}
|
58
|
+
self.run_loop = RunLoop.run(default_args.merge(args))
|
59
|
+
else
|
60
|
+
|
61
|
+
sdk = ENV['SDK_VERSION'] || SimLauncher::SdkDetector.new().latest_sdk_version
|
62
|
+
path = Calabash::Cucumber::SimulatorHelper.app_bundle_or_raise(app_path)
|
63
|
+
if ENV['RESET_BETWEEN_SCENARIOS']=="1"
|
64
|
+
reset_app_jail(sdk, path)
|
65
|
+
end
|
66
|
+
|
67
|
+
if simulator_target?
|
68
|
+
device = (ENV['DEVICE'] || 'iphone').to_sym
|
69
|
+
default_args = {:app => path, :device => device}
|
70
|
+
self.run_loop = RunLoop.run(default_args.merge(args))
|
71
|
+
else
|
72
|
+
## sim launcher
|
73
|
+
Calabash::Cucumber::SimulatorHelper.relaunch(path, sdk, ENV['DEVICE'] || 'iphone', args)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
ensure_connectivity
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def ensure_connectivity
|
82
|
+
begin
|
83
|
+
max_retry_count = (ENV['MAX_CONNECT_RETRY'] || 10).to_i
|
84
|
+
timeout = (ENV['CONNECT_TIMEOUT'] || 30).to_i
|
85
|
+
retry_count = 0
|
86
|
+
connected = false
|
87
|
+
puts "Waiting for App to be ready"
|
88
|
+
until connected do
|
89
|
+
raise "MAX_RETRIES" if retry_count == max_retry_count
|
90
|
+
retry_count += 1
|
91
|
+
begin
|
92
|
+
Timeout::timeout(timeout, CalabashLauncherTimeoutErr) do
|
93
|
+
until connected
|
94
|
+
begin
|
95
|
+
connected = (ping_app == '200')
|
96
|
+
break if connected
|
97
|
+
rescue Exception => e
|
98
|
+
#p e
|
99
|
+
#retry
|
100
|
+
ensure
|
101
|
+
sleep 1 unless connected
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
rescue CalabashLauncherTimeoutErr => e
|
106
|
+
puts "Timed out...Retry.."
|
107
|
+
end
|
108
|
+
end
|
109
|
+
rescue e
|
110
|
+
p e
|
111
|
+
msg = "Unable to make connection to Calabash Server at #{ENV['DEVICE_ENDPOINT']|| "http://localhost:37265/"}\n"
|
112
|
+
msg << "Make sure you don't have a firewall blocking traffic to #{ENV['DEVICE_ENDPOINT']|| "http://localhost:37265/"}.\n"
|
113
|
+
raise msg
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def ping_app
|
118
|
+
url = URI.parse(ENV['DEVICE_ENDPOINT']|| "http://localhost:37265/")
|
119
|
+
|
120
|
+
http = Net::HTTP.new(url.host, url.port)
|
121
|
+
res = http.start do |sess|
|
122
|
+
sess.request Net::HTTP::Get.new "version"
|
123
|
+
end
|
124
|
+
status = res.code
|
125
|
+
begin
|
126
|
+
http.finish if http and http.started?
|
127
|
+
rescue Exception => e
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
if status=='200'
|
132
|
+
version_body = JSON.parse(res.body)
|
133
|
+
if version_body['iOS_version']
|
134
|
+
self.ios_version = version_body['iOS_version']
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
status
|
139
|
+
end
|
140
|
+
|
141
|
+
def stop
|
142
|
+
RunLoop.stop(run_loop)
|
143
|
+
end
|
144
|
+
|
145
|
+
def app_path
|
146
|
+
ENV['APP_BUNDLE_PATH'] || (defined?(APP_BUNDLE_PATH) && APP_BUNDLE_PATH)
|
147
|
+
end
|
148
|
+
|
149
|
+
def calabash_notify(world)
|
150
|
+
if world.respond_to?(:on_launch)
|
151
|
+
world.on_launch
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
@@ -3,6 +3,7 @@ require 'calabash-cucumber/tests_helpers'
|
|
3
3
|
require 'calabash-cucumber/keyboard_helpers'
|
4
4
|
require 'calabash-cucumber/wait_helpers'
|
5
5
|
require 'calabash-cucumber/location'
|
6
|
+
require 'calabash-cucumber/launcher'
|
6
7
|
require 'net/http'
|
7
8
|
require 'test/unit/assertions'
|
8
9
|
require 'json'
|
@@ -99,7 +100,6 @@ module Calabash
|
|
99
100
|
end
|
100
101
|
|
101
102
|
|
102
|
-
|
103
103
|
#not officially supported yet
|
104
104
|
#def change_slider_value_to(q, value)
|
105
105
|
# target = value.to_f
|
data/scripts/launch.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
########################################
|
2
|
+
# #
|
3
|
+
# Important Note #
|
4
|
+
# #
|
5
|
+
# When running calabash-ios tests at #
|
6
|
+
# www.lesspainful.com #
|
7
|
+
# this file will be overwritten by #
|
8
|
+
# a file which automates #
|
9
|
+
# app launch on devices. #
|
10
|
+
# #
|
11
|
+
# Don't rely on this file being #
|
12
|
+
# present when running at #
|
13
|
+
# www.lesspainful.com. #
|
14
|
+
# #
|
15
|
+
# Only put stuff here to automate #
|
16
|
+
# iOS Simulator. #
|
17
|
+
# #
|
18
|
+
# You can put your app bundle path #
|
19
|
+
# for automating simulator app start: #
|
20
|
+
# Uncomment APP_BUNDLE_PATH =.. #
|
21
|
+
# #
|
22
|
+
########################################
|
23
|
+
|
24
|
+
require 'calabash-cucumber/launcher'
|
25
|
+
|
26
|
+
# Uncomment and replace ?? appropriately
|
27
|
+
# This should point to your Simulator build
|
28
|
+
# which includes calabash framework
|
29
|
+
# this is usually the Calabash build configuration
|
30
|
+
# of your production target.
|
31
|
+
#APP_BUNDLE_PATH = "~/Library/Developer/Xcode/DerivedData/??/Build/Products/Calabash-iphonesimulator/??.app"
|
32
|
+
#
|
33
|
+
|
34
|
+
|
35
|
+
Before do |scenario|
|
36
|
+
@calabash_launcher = Calabash::Cucumber::Launcher.new
|
37
|
+
@calabash_launcher.relaunch
|
38
|
+
@calabash_launcher.calabash_notify(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
After do |scenario|
|
42
|
+
unless @calabash_launcher.calabash_no_stop?
|
43
|
+
if @calabash_launcher.active?
|
44
|
+
@calabash_launcher.stop
|
45
|
+
else
|
46
|
+
Calabash::Cucumber::SimulatorHelper.stop
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: testautoi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.142
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
12
|
+
date: 2013-04-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cucumber
|
@@ -146,7 +146,7 @@ dependencies:
|
|
146
146
|
requirements:
|
147
147
|
- - '='
|
148
148
|
- !ruby/object:Gem::Version
|
149
|
-
version: 0.0.
|
149
|
+
version: 0.0.7
|
150
150
|
type: :runtime
|
151
151
|
prerelease: false
|
152
152
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -154,7 +154,7 @@ dependencies:
|
|
154
154
|
requirements:
|
155
155
|
- - '='
|
156
156
|
- !ruby/object:Gem::Version
|
157
|
-
version: 0.0.
|
157
|
+
version: 0.0.7
|
158
158
|
- !ruby/object:Gem::Dependency
|
159
159
|
name: awesome_print
|
160
160
|
requirement: !ruby/object:Gem::Requirement
|
@@ -215,6 +215,7 @@ files:
|
|
215
215
|
- bin/testautoi
|
216
216
|
- calabash-cucumber.gemspec
|
217
217
|
- doc/calabash-ios-help.txt
|
218
|
+
- doc/x-platform-testing.md
|
218
219
|
- epl-v10.html
|
219
220
|
- features-skeleton/my_first.feature
|
220
221
|
- features-skeleton/step_definitions/calabash_steps.rb
|
@@ -230,6 +231,7 @@ files:
|
|
230
231
|
- lib/calabash-cucumber/ibase.rb
|
231
232
|
- lib/calabash-cucumber/keyboard_helpers.rb
|
232
233
|
- lib/calabash-cucumber/launch/simulator_helper.rb
|
234
|
+
- lib/calabash-cucumber/launcher.rb
|
233
235
|
- lib/calabash-cucumber/location.rb
|
234
236
|
- lib/calabash-cucumber/operations.rb
|
235
237
|
- lib/calabash-cucumber/resources/cell_swipe_ios4_ipad.base64
|
@@ -317,7 +319,9 @@ files:
|
|
317
319
|
- scripts/data/.GlobalPreferences.plist
|
318
320
|
- scripts/data/clients.plist
|
319
321
|
- scripts/data/com.apple.Accessibility.plist
|
322
|
+
- scripts/launch.rb
|
320
323
|
- scripts/reset_simulator.scpt
|
324
|
+
- staticlib/calabash.framework.zip
|
321
325
|
homepage:
|
322
326
|
licenses: []
|
323
327
|
post_install_message:
|