calabash-android 0.1.0 → 0.2.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,13 @@
1
+ 0.2.0.pre2:
2
+ A HTTP server has been introduced in the test server so that we no
3
+ longer use socket for communication.
4
+
5
+ 0.2.0.pre1:
6
+ The concept of a Device has been introduced. It will handle all setup
7
+ and communication with physical devices or emulators.
8
+ The device class is needed because we want to interact with more than
9
+ one device at the same time from Calabash.
10
+
1
11
  0.1.0:
2
12
  Added support for testing Xamarin Mono for Android apps.
3
13
  Currently only Release builds are supported.
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'fileutils'
4
+ require 'calabash-android/helpers'
5
+
4
6
 
5
7
  require File.join(File.dirname(__FILE__), "calabash-android-helpers")
6
8
  require File.join(File.dirname(__FILE__), "calabash-android-generate")
@@ -14,6 +16,14 @@ require File.join(File.dirname(__FILE__), "calabash-android-submit")
14
16
  @source_dir = File.join(File.dirname(__FILE__), '..', 'features-skeleton')
15
17
  @script_dir = File.join(File.dirname(__FILE__), '..', 'scripts')
16
18
 
19
+ def is_apk_file?(file_path)
20
+ file_path.end_with? ".apk" and File.exist? file_path
21
+ end
22
+
23
+ def relative_to_full_path(file_path)
24
+ File.expand_path(File.join(ENV["PWD"], file_path))
25
+ end
26
+
17
27
  if (ARGV.length == 0)
18
28
  print_usage
19
29
  exit 0
@@ -23,10 +33,17 @@ if cmd == 'help'
23
33
  print_help
24
34
  exit 0
25
35
  elsif cmd == 'build'
26
- calabash_build(ARGV)
36
+ puts "Please specify the app you want to test" if (ARGV.empty? or not is_apk_file?(ARGV.first))
37
+ while not ARGV.empty? and is_apk_file?(ARGV.first)
38
+ calabash_build(relative_to_full_path(ARGV.shift))
39
+ end
27
40
  exit 0
28
41
  elsif cmd == 'run'
29
- calabash_run(ARGV)
42
+ if ARGV.empty? or not is_apk_file?(ARGV.first)
43
+ calabash_run(nil, ARGV)
44
+ else
45
+ calabash_run(ARGV.shift)
46
+ end
30
47
  exit 0
31
48
  elsif cmd == 'gen'
32
49
  calabash_scaffold
@@ -1,8 +1,10 @@
1
- def calabash_build(args)
2
- run_setup_if_settings_does_not_exist
1
+ def calabash_build(app)
3
2
 
4
- @settings = JSON.parse(IO.read(".calabash_settings"))
5
- test_server_template_dir = File.join(File.dirname(__FILE__), '..', 'test-server')
3
+
4
+
5
+ keystore = read_keystore_info()
6
+
7
+ test_server_template_dir = File.join(File.dirname(__FILE__), '..', 'test-server')
6
8
 
7
9
  Dir.mktmpdir do |workspace_dir|
8
10
 
@@ -15,14 +17,14 @@ def calabash_build(args)
15
17
  ant_executable,
16
18
  "clean",
17
19
  "package",
18
- "-Dtested.package_name=#{@settings["package_name"]}",
19
- "-Dtested.main_activity=#{@settings["activity_name"]}",
20
- "-Dtested.project.apk=#{@settings["app_path"]}",
21
- "-Dandroid.api.level=#{@settings["api_level"]}",
22
- "-Dkey.store=#{@settings["keystore_location"]}",
23
- "-Dkey.store.password=#{@settings["keystore_password"]}",
24
- "-Dkey.alias=#{@settings["keystore_alias"]}",
25
- "-Dkey.alias.password=#{@settings["keystore_alias_password"]}",
20
+ "-Dtested.package_name=#{package_name(app)}",
21
+ "-Dtested.main_activity=#{main_activity(app)}",
22
+ "-Dtested.project.apk=#{app}",
23
+ "-Dandroid.api.level=#{api_level}",
24
+ "-Dkey.store=#{keystore["keystore_location"]}",
25
+ "-Dkey.store.password=#{keystore["keystore_password"]}",
26
+ "-Dkey.alias=#{keystore["keystore_alias"]}",
27
+ "-Dkey.alias.password=#{keystore["keystore_alias_password"]}",
26
28
  ]
27
29
  STDOUT.sync = true
28
30
  IO.popen(args.join(" ")) do |io|
@@ -35,8 +37,22 @@ def calabash_build(args)
35
37
  }
36
38
 
37
39
  test_apk = File.join(@test_server_dir, "bin", "Test.apk")
38
- FileUtils.cp(test_apk, File.join(@support_dir, "Test.apk"))
39
- puts "Done building the test server. Moved it to features/support/Test.apk"
40
+ test_server_file_name = "#{checksum(app)}.apk"
41
+ FileUtils.cp(test_apk, File.join(@support_dir, test_server_file_name))
42
+ puts "Done building the test server. Moved it to features/support/#{test_server_file_name}"
43
+ end
44
+ end
45
+
46
+ def read_keystore_info
47
+ if File.exist? ".calabash_settings"
48
+ JSON.parse(IO.read(".calabash_settings"))
49
+ else
50
+ {
51
+ "keystore_location" => "#{ENV["HOME"]}/.android/debug.keystore",
52
+ "keystore_password" => "android",
53
+ "keystore_alias" => "androiddebugkey",
54
+ "keystore_alias_password" => "android"
55
+ }
40
56
  end
41
57
  end
42
58
 
@@ -2,7 +2,6 @@
2
2
  def calabash_scaffold
3
3
  if File.exists?(@features_dir)
4
4
  puts "A features directory already exists. Stopping..."
5
- #puts "Run calabash-android update for update instructions."
6
5
  exit 1
7
6
  end
8
7
  msg("Question") do
@@ -43,25 +43,10 @@ def print_help
43
43
  print_usage
44
44
  end
45
45
 
46
-
47
46
  def is_json?(str)
48
47
  str[0..0] == '{'
49
48
  end
50
49
 
51
- def run_build_if_test_server_does_not_exist
52
- unless File.exists?(File.join(@support_dir, "Test.apk"))
53
- puts "Could not find the test server"
54
- puts "Should I run calabash-android build for you?"
55
- puts "Please answer yes (y) or no (n)"
56
- if ['yes', 'y'].include? STDIN.gets.chomp
57
- calabash_build([])
58
- else
59
- puts "Please run: calabash-android build"
60
- exit 1
61
- end
62
- end
63
- end
64
-
65
50
  def run_setup_if_settings_does_not_exist
66
51
  unless File.exists?(".calabash_settings")
67
52
  puts "Could not find .calabash_settings."
@@ -1,5 +1,4 @@
1
- def calabash_run(args)
2
- run_build_if_test_server_does_not_exist
1
+ def calabash_run(app_path)
3
2
 
4
3
  old_runner = "android.test.InstrumentationTestRunner"
5
4
  new_rummer = "sh.calaba.instrumentationbackend.CalabashInstrumentationTestRunner"
@@ -13,14 +12,17 @@ def calabash_run(args)
13
12
  exit
14
13
  end
15
14
 
16
- settings = JSON.parse(IO.read(".calabash_settings"))
17
-
18
- env ="PACKAGE_NAME=#{settings["package_name"]} "\
19
- "TEST_PACKAGE_NAME=#{settings["package_name"]}.test "\
20
- "APP_PATH=#{settings["app_path"]} "\
21
- "TEST_APP_PATH=features/support/Test.apk "\
22
- "TEST_SERVER_PORT=34777"
23
-
15
+ if app_path
16
+ test_server_path = "features/support/#{checksum(app_path)}.apk"
17
+ env = "PACKAGE_NAME=#{package_name(app_path)} "\
18
+ "TEST_PACKAGE_NAME=#{package_name(test_server_path)} "\
19
+ "APP_PATH=#{app_path} "\
20
+ "TEST_APP_PATH=#{test_server_path} "\
21
+ "TEST_SERVER_PORT=34777"
22
+ else
23
+ env = ""
24
+ end
25
+
24
26
  STDOUT.sync = true
25
27
  cmd = "cucumber -c #{ARGV.join(" ")} #{env}"
26
28
  puts cmd
@@ -8,8 +8,9 @@ Before do |scenario|
8
8
  feature_name = scenario.feature.name
9
9
  if FeatureNameMemory.feature_name != feature_name
10
10
  log "Is first scenario - reinstalling apps"
11
- uninstall_apps
11
+ uninstall_app(ENV["TEST_APP_PATH"])
12
12
  install_app(ENV["TEST_APP_PATH"])
13
+ uninstall_app(ENV["APP_PATH"])
13
14
  install_app(ENV["APP_PATH"])
14
15
  FeatureNameMemory.feature_name = feature_name
15
16
  end
@@ -3,26 +3,5 @@ require 'calabash-android/management/adb'
3
3
  Before do |scenario|
4
4
 
5
5
  return if scenario.failed? #No need to start the server is anything before this has failed.
6
- cmd = "#{adb_command} shell am instrument -w -e class sh.calaba.instrumentationbackend.InstrumentationBackend #{ENV['TEST_PACKAGE_NAME']}/sh.calaba.instrumentationbackend.CalabashInstrumentationTestRunner"
7
- log "Starting test server using:"
8
- log cmd
9
- if is_windows?
10
- system(%Q(start /MIN cmd /C #{cmd}))
11
- else
12
- `#{cmd} 1>&2 &`
13
- end
14
-
15
- sleep 2
16
- begin
17
- connect_to_test_server
18
- log "Connection established"
19
- rescue Exception => e
20
- log "Exception:#{e.backtrace}"
21
- end
22
- end
23
-
24
-
25
-
26
- After do |scenario|
27
- disconnect_from_test_server
6
+ start_test_server_in_background
28
7
  end
@@ -0,0 +1,54 @@
1
+ require 'rexml/document'
2
+ require 'rexml/xpath'
3
+
4
+ include REXML
5
+
6
+ def package_name(app)
7
+ require 'rexml/document'
8
+ require 'rexml/xpath'
9
+
10
+ manifest = Document.new(manifest(app))
11
+ manifest.root.attributes['package']
12
+ end
13
+
14
+ def main_activity(app)
15
+ manifest = Document.new(manifest(app))
16
+ main_activity = manifest.elements["//action[@name='android.intent.action.MAIN']/../.."].attributes['name']
17
+ #Handle situation where main activity is on the form '.class_name'
18
+ if main_activity.start_with? "."
19
+ main_activity = package_name(app) + main_activity
20
+ elsif not main_activity.include? "." #This is undocumentet behaviour but Android seems to accept shorthand naming that does not start with '.'
21
+ main_activity = "#{package_name(app)}.#{main_activity}"
22
+ end
23
+ main_activity
24
+ end
25
+
26
+ def api_level
27
+ api_levels = Dir["#{ENV["ANDROID_HOME"]}/platforms/android-*"].collect{|platform| platform.split("-").last.to_i}.sort
28
+ if api_levels.empty?
29
+ raise "Android SDK not found. Please install one of more using #{ENV["ANDROID_HOME"]}/tools/android"
30
+ end
31
+
32
+ api_levels = api_levels.find_all {|l| l > 7}
33
+ if api_levels.empty?
34
+ raise "Android SDK above 7 not found. Please install one of more using #{ENV["ANDROID_HOME"]}/tools/android"
35
+ end
36
+ api_levels.first
37
+ end
38
+
39
+ def manifest(app)
40
+ require 'tmpdir'
41
+ dir = Dir.mktmpdir
42
+ FileUtils.cp(app, File.join(dir, "app.apk"))
43
+
44
+ system "unzip -d #{dir} #{app} AndroidManifest.xml"
45
+
46
+ `java -jar #{File.dirname(__FILE__)}/lib/AXMLPrinter2.jar #{dir}/AndroidManifest.xml`
47
+ end
48
+
49
+ def checksum(file_path)
50
+ require 'digest/md5'
51
+ Digest::MD5.hexdigest(File.read(file_path))
52
+ end
53
+
54
+
@@ -1,11 +0,0 @@
1
- def adb_command
2
- if is_windows?
3
- %Q("#{ENV["ANDROID_HOME"]}\\platform-tools\\adb.exe" #{ENV["ADB_DEVICE_ARG"]})
4
- else
5
- %Q(#{ENV["ANDROID_HOME"]}/platform-tools/adb #{ENV["ADB_DEVICE_ARG"]})
6
- end
7
- end
8
-
9
- def is_windows?
10
- ENV["OS"] == "Windows_NT"
11
- end
@@ -1,8 +1,10 @@
1
1
  require 'json'
2
+ require 'net/http'
2
3
  require 'rubygems'
3
4
  require 'json'
4
5
  require 'socket'
5
6
  require 'timeout'
7
+ require 'calabash-android/helpers'
6
8
 
7
9
 
8
10
  module Calabash module Android
@@ -15,19 +17,7 @@ module Operations
15
17
  end
16
18
 
17
19
  def take_screenshot
18
- path = ENV["SCREENSHOT_PATH_PREFIX"] || "results"
19
- FileUtils.mkdir_p path unless File.exist? path
20
- filename_prefix = FeatureNameMemory.feature_name.gsub(/\s+/, '_').downcase
21
- begin
22
- Timeout.timeout(30) do
23
- file_name = "#{path}/#{filename_prefix}_#{StepCounter.step_line}.png"
24
- log "Taking screenshoot to #{file_name} from device: #{ENV['ADB_DEVICE_ARG']}"
25
- system("java -jar #{File.dirname(__FILE__)}/lib/screenShotTaker.jar #{file_name} #{ENV['ADB_DEVICE_ARG']}")
26
- log "Screenshot stored in: #{file_name}"
27
- end
28
- rescue Timeout::Error
29
- raise Exception, "take_screenshot timed out"
30
- end
20
+ Device.default_device.take_screenshot
31
21
  end
32
22
 
33
23
  def macro(txt)
@@ -39,31 +29,23 @@ module Operations
39
29
  end
40
30
 
41
31
  def performAction(action, *arguments)
42
- log "Action: #{action} - Params: #{arguments.join(', ')}"
32
+ Device.default_device.perform_action(action, *arguments)
33
+ end
43
34
 
44
- action = {"command" => action, "arguments" => arguments}
35
+ def install_app(app_path)
36
+ Device.default_device.install_app(app_path)
37
+ end
45
38
 
46
- Timeout.timeout(300) do
47
- begin
48
- @@client.send(action.to_json + "\n", 0) #force_encoding('UTF-8') seems to be missing from JRuby
49
- result = @@client.readline
50
- rescue Exception => e
51
- log "Error communicating with test server: #{e}"
52
- raise e
53
- end
54
- log "Result:'" + result.strip + "'"
55
- raise "Empty result from TestServer" if result.chomp.empty?
56
- result = JSON.parse(result)
57
- if not result["success"] then
58
- take_screenshot
59
- raise result["message"].to_s
60
- end
61
- return result
62
- end
63
- rescue Timeout::Error
64
- raise Exception, "Step timed out"
39
+ def uninstall_apps
40
+ Device.default_device.uninstall_app(ENV["TEST_PACKAGE_NAME"])
41
+ Device.default_device.uninstall_app(ENV["PACKAGE_NAME"])
65
42
  end
66
43
 
44
+ def start_test_server_in_background
45
+ Device.default_device.start_test_server_in_background()
46
+ end
47
+
48
+
67
49
  def wait_for(timeout, &block)
68
50
  begin
69
51
  Timeout::timeout(timeout) do
@@ -93,16 +75,6 @@ module Operations
93
75
  raise "Not yet implemented."
94
76
  end
95
77
 
96
-
97
- ##adb
98
- def adb_command
99
- if is_windows?
100
- %Q("#{ENV["ANDROID_HOME"]}\\platform-tools\\adb.exe" #{ENV["ADB_DEVICE_ARG"]})
101
- else
102
- %Q(#{ENV["ANDROID_HOME"]}/platform-tools/adb #{ENV["ADB_DEVICE_ARG"]})
103
- end
104
- end
105
-
106
78
  def is_windows?
107
79
  ENV["OS"] == "Windows_NT"
108
80
  end
@@ -110,41 +82,179 @@ module Operations
110
82
 
111
83
  ### app life cycle
112
84
  def connect_to_test_server
113
- log `#{adb_command} forward tcp:#{ENV["TEST_SERVER_PORT"]} tcp:7101`
114
-
115
- end_time = Time.now + 60
116
- begin
117
- Timeout.timeout(10) do
118
- @@client = TCPSocket.open('127.0.0.1',ENV["TEST_SERVER_PORT"])
119
- @@client.send("Ping!\n",0)
120
- log "Got '#{@@client.readline.strip}' from testserver"
121
- end
122
- rescue Exception => e
123
- log "Got exception:#{e}. Retrying!"
124
- sleep(1)
125
- retry unless Time.now > end_time
126
- end
85
+ puts "Explicit calls to connect_to_test_server should be removed."
86
+ puts "Please take a look in your hooks file for calls to this methods."
87
+ puts "(Hooks are stored in features/support)"
127
88
  end
128
89
 
129
90
  def disconnect_from_test_server
130
- log "Closing connection to test"
131
- @@client.close
91
+ puts "Explicit calls to disconnect_from_test_server should be removed."
92
+ puts "Please take a look in your hooks file for calls to this methods."
93
+ puts "(Hooks are stored in features/support)"
132
94
  end
133
95
 
96
+ class Device
97
+ @@default_device = nil
134
98
 
99
+ def self.default_device
100
+ unless @@default_device
101
+ @@default_device = Device.new(ENV["ADB_DEVICE_ARG"], ENV["TEST_SERVER_PORT"], ENV["APP_PATH"], ENV["TEST_APP_PATH"])
102
+ end
103
+ @@default_device
104
+ end
105
+
106
+ def make_default_device
107
+ @@default_device = self
108
+ end
135
109
 
136
- ##location
137
- def set_gps_coordinates_from_location(location)
138
- require 'geocoder'
139
- results = Geocoder.search(location)
140
- raise Exception, "Got no results for #{location}" if results.empty?
110
+ def initialize(serial, server_port, app_path, test_server_path)
111
+ @serial = serial
112
+ @server_port = server_port
113
+ @app_path = app_path
114
+ @test_server_path = test_server_path
141
115
 
142
- best_result = results.first
143
- set_gps_coordinates(best_result.latitude, best_result.longitude)
144
- end
116
+ puts "#{adb_command} forward tcp:b#{server_port} tcp:7102"
117
+ log `#{adb_command} forward tcp:#{server_port} tcp:7102`
118
+ =begin
119
+ begin
120
+ Timeout::timeout(15) do
121
+ puts http("/ping")
122
+ end
123
+ rescue Timeout::Error
124
+ msg = "Unable to make connection to Calabash Test Server at http://127.0.0.1:#{@server_port}/\n"
125
+ msg << "Please check the logcat output for more info about what happened\n"
126
+ raise msg
127
+ end
128
+ =end
129
+ end
130
+
131
+ def reinstall_apps()
132
+ uninstall_app(package_name(@app_path))
133
+ install_app(@app_path)
134
+ uninstall_app(package_name(@test_server_path))
135
+ install_app(@test_server_path)
136
+ end
137
+
138
+ def install_app(app_path)
139
+ cmd = "#{adb_command} install #{app_path}"
140
+ log "Installing: #{app_path}"
141
+ result = `#{cmd}`
142
+ if result.include? "Success"
143
+ log "Success"
144
+ else
145
+ log "#Failure"
146
+ log "'#{cmd}' said:"
147
+ log result.strip
148
+ raise "Could not install app #{app_path}: #{result.strip}"
149
+ end
150
+ end
145
151
 
146
- def set_gps_coordinates(latitude, longitude)
147
- performAction('set_gps_coordinates', latitude, longitude)
152
+ def uninstall_app(package_name)
153
+ log "Uninstalling: #{package_name}"
154
+ log `#{adb_command} uninstall #{package_name}`
155
+ end
156
+
157
+ def perform_action(action, *arguments)
158
+ log "Action: #{action} - Params: #{arguments.join(', ')}"
159
+
160
+ params = {"command" => action, "arguments" => arguments}
161
+
162
+ Timeout.timeout(300) do
163
+ begin
164
+ result = http("/", params)
165
+ rescue Exception => e
166
+ log "Error communicating with test server: #{e}"
167
+ raise e
168
+ end
169
+ log "Result:'" + result.strip + "'"
170
+ raise "Empty result from TestServer" if result.chomp.empty?
171
+ result = JSON.parse(result)
172
+ if not result["success"] then
173
+ take_screenshot
174
+ raise result["message"].to_s
175
+ end
176
+ return result
177
+ end
178
+ rescue Timeout::Error
179
+ raise Exception, "Step timed out"
180
+ end
181
+
182
+ def http(path, data = {})
183
+ begin
184
+ http = Net::HTTP.new "127.0.0.1", @server_port
185
+ resp, data = http.post(path, "command=#{data.to_json}", {})
186
+ data
187
+ rescue EOFError
188
+ sleep 0.5
189
+ retry
190
+ end
191
+ end
192
+
193
+ def take_screenshot
194
+ path = ENV["SCREENSHOT_PATH_PREFIX"] || "results"
195
+ FileUtils.mkdir_p path unless File.exist? path
196
+ filename_prefix = FeatureNameMemory.feature_name.gsub(/\s+/, '_').downcase
197
+ begin
198
+ Timeout.timeout(30) do
199
+ file_name = "#{path}/#{filename_prefix}_#{StepCounter.step_line}.png"
200
+ log "Taking screenshoot to #{file_name} from device: #{@serial}"
201
+ system("java -jar #{File.dirname(__FILE__)}/lib/screenShotTaker.jar #{file_name} #{device_args}")
202
+ log "Screenshot stored in: #{file_name}"
203
+ end
204
+ rescue Timeout::Error
205
+ raise Exception, "take_screenshot timed out"
206
+ end
207
+ end
208
+
209
+ def adb_command
210
+ if is_windows?
211
+ %Q("#{ENV["ANDROID_HOME"]}\\platform-tools\\adb.exe" #{device_args})
212
+ else
213
+ %Q(#{ENV["ANDROID_HOME"]}/platform-tools/adb #{device_args})
214
+ end
215
+ end
216
+
217
+ def device_args
218
+ if @serial
219
+ "-s #{@serial}"
220
+ else
221
+ ""
222
+ end
223
+ end
224
+
225
+ def is_windows?
226
+ ENV["OS"] == "Windows_NT"
227
+ end
228
+
229
+ def start_test_server_in_background
230
+ test_server_package = package_name(@test_server_path)
231
+ cmd = "#{adb_command} shell am instrument -w -e class sh.calaba.instrumentationbackend.InstrumentationBackend #{test_server_package}/sh.calaba.instrumentationbackend.CalabashInstrumentationTestRunner"
232
+ log "Starting test server using:"
233
+ log cmd
234
+ if is_windows?
235
+ system(%Q(start /MIN cmd /C #{cmd}))
236
+ else
237
+ `#{cmd} 1>&2 &`
238
+ end
239
+ end
240
+
241
+ def log(message)
242
+ $stdout.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{message}" if (ARGV.include? "-v" or ARGV.include? "--verbose")
243
+ end
244
+
245
+ ##location
246
+ def set_gps_coordinates_from_location(location)
247
+ require 'geocoder'
248
+ results = Geocoder.search(location)
249
+ raise Exception, "Got no results for #{location}" if results.empty?
250
+
251
+ best_result = results.first
252
+ set_gps_coordinates(best_result.latitude, best_result.longitude)
253
+ end
254
+
255
+ def set_gps_coordinates(latitude, longitude)
256
+ perform_action('set_gps_coordinates', latitude, longitude)
257
+ end
148
258
  end
149
259
 
150
260
 
@@ -167,7 +277,6 @@ module Operations
167
277
  query(q).map {|e| e['html']}
168
278
  end
169
279
 
170
-
171
280
  def set_text(uiquery, txt)
172
281
  raise "Currently queries are only supported for webviews" unless uiquery.start_with? "webView"
173
282