calabash 1.9.9.pre3 → 2.0.0.prelegacy

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -33
  3. data/bin/calabash +45 -36
  4. data/lib/calabash.rb +137 -13
  5. data/lib/calabash/android.rb +6 -0
  6. data/lib/calabash/android/adb.rb +25 -1
  7. data/lib/calabash/android/application.rb +14 -3
  8. data/lib/calabash/android/build/builder.rb +16 -3
  9. data/lib/calabash/android/build/java_keystore.rb +10 -0
  10. data/lib/calabash/android/build/resigner.rb +23 -1
  11. data/lib/calabash/android/build/test_server.rb +2 -0
  12. data/lib/calabash/android/defaults.rb +1 -0
  13. data/lib/calabash/android/device.rb +55 -3
  14. data/lib/calabash/android/environment.rb +10 -0
  15. data/lib/calabash/android/interactions.rb +106 -3
  16. data/lib/calabash/android/legacy.rb +143 -0
  17. data/lib/calabash/android/lib/TestServer.apk +0 -0
  18. data/lib/calabash/android/life_cycle.rb +6 -4
  19. data/lib/calabash/android/physical_buttons.rb +12 -1
  20. data/lib/calabash/android/screenshot.rb +1 -0
  21. data/lib/calabash/android/scroll.rb +115 -0
  22. data/lib/calabash/android/server.rb +3 -1
  23. data/lib/calabash/android/text.rb +16 -25
  24. data/lib/calabash/application.rb +29 -0
  25. data/lib/calabash/cli/build.rb +15 -1
  26. data/lib/calabash/cli/console.rb +9 -5
  27. data/lib/calabash/cli/generate.rb +5 -0
  28. data/lib/calabash/cli/helpers.rb +7 -1
  29. data/lib/calabash/cli/resign.rb +1 -0
  30. data/lib/calabash/cli/run.rb +10 -6
  31. data/lib/calabash/cli/setup_keystore.rb +2 -0
  32. data/lib/calabash/color.rb +7 -0
  33. data/lib/calabash/console_helpers.rb +4 -2
  34. data/lib/calabash/defaults.rb +1 -0
  35. data/lib/calabash/device.rb +8 -9
  36. data/lib/calabash/environment.rb +5 -0
  37. data/lib/calabash/gestures.rb +159 -66
  38. data/lib/calabash/http/retriable_client.rb +3 -1
  39. data/lib/calabash/interactions.rb +68 -5
  40. data/lib/calabash/ios.rb +4 -0
  41. data/lib/calabash/ios/application.rb +8 -1
  42. data/lib/calabash/ios/conditions.rb +3 -1
  43. data/lib/calabash/ios/date_picker.rb +412 -0
  44. data/lib/calabash/ios/defaults.rb +1 -0
  45. data/lib/calabash/ios/device.rb +1 -0
  46. data/lib/calabash/ios/device/device_implementation.rb +33 -16
  47. data/lib/calabash/ios/device/gestures_mixin.rb +202 -48
  48. data/lib/calabash/ios/device/ipad_1x_2x_mixin.rb +253 -0
  49. data/lib/calabash/ios/device/keyboard_mixin.rb +2 -0
  50. data/lib/calabash/ios/device/rotation_mixin.rb +11 -8
  51. data/lib/calabash/ios/device/routes/condition_route_mixin.rb +1 -0
  52. data/lib/calabash/ios/device/routes/handle_route_mixin.rb +5 -1
  53. data/lib/calabash/ios/device/routes/map_route_mixin.rb +1 -0
  54. data/lib/calabash/ios/device/routes/response_parser.rb +1 -0
  55. data/lib/calabash/ios/device/routes/uia_route_mixin.rb +44 -6
  56. data/lib/calabash/ios/device/runtime_attributes.rb +4 -5
  57. data/lib/calabash/ios/device/text_mixin.rb +2 -0
  58. data/lib/calabash/ios/device/uia_keyboard_mixin.rb +9 -0
  59. data/lib/calabash/ios/device/uia_mixin.rb +1 -0
  60. data/lib/calabash/ios/gestures.rb +82 -8
  61. data/lib/calabash/ios/interactions.rb +30 -1
  62. data/lib/calabash/ios/orientation.rb +21 -21
  63. data/lib/calabash/ios/runtime.rb +154 -2
  64. data/lib/calabash/ios/slider.rb +70 -0
  65. data/lib/calabash/ios/text.rb +11 -47
  66. data/lib/calabash/ios/uia.rb +24 -2
  67. data/lib/calabash/legacy.rb +7 -0
  68. data/lib/calabash/lib/skeleton/config/cucumber.yml +1 -3
  69. data/lib/calabash/lib/skeleton/features/support/dry_run.rb +8 -0
  70. data/lib/calabash/lib/skeleton/features/support/env.rb +15 -1
  71. data/lib/calabash/life_cycle.rb +78 -32
  72. data/lib/calabash/location.rb +2 -1
  73. data/lib/calabash/orientation.rb +0 -1
  74. data/lib/calabash/page.rb +51 -5
  75. data/lib/calabash/patch.rb +1 -0
  76. data/lib/calabash/patch/array.rb +7 -7
  77. data/lib/calabash/query.rb +17 -2
  78. data/lib/calabash/query_result.rb +14 -0
  79. data/lib/calabash/screenshot.rb +28 -8
  80. data/lib/calabash/text.rb +105 -8
  81. data/lib/calabash/utility.rb +6 -6
  82. data/lib/calabash/version.rb +1 -1
  83. data/lib/calabash/wait.rb +37 -11
  84. metadata +14 -7
@@ -1,7 +1,8 @@
1
1
  require 'geocoder'
2
2
 
3
3
  module Calabash
4
- # @!visibility private
4
+
5
+ # An API for setting the location of your app.
5
6
  module Location
6
7
  # Simulates gps location of the device/simulator.
7
8
  # @note Seems UIAutomation is broken here on physical devices on iOS 7.1
@@ -2,7 +2,6 @@ module Calabash
2
2
 
3
3
  # Methods for querying an app's orientation and for rotating the app into
4
4
  # different orientations.
5
- # @!visibility private
6
5
  module Orientation
7
6
  # Is the device in the portrait orientation?
8
7
  #
data/lib/calabash/page.rb CHANGED
@@ -1,29 +1,75 @@
1
1
  module Calabash
2
+
3
+ # A base class for the Page Object Model (POM) or Page Object Pattern.
4
+ #
5
+ # We recommend the POM for testing cross-platform apps.
6
+ #
7
+ # We have a great examples of using the POM in the Calabash 2.0 repository.
8
+ # * https://github.com/calabash/calabash/tree/develop/samples/wordpress
9
+ # * https://github.com/calabash/calabash/tree/develop/samples/shared-page-logic
2
10
  class Page
3
11
  # For auto-completion
4
12
  include Calabash
5
13
 
14
+ # @!visibility private
6
15
  def self.inherited(subclass)
7
16
  # Define the page into global scope
8
- name = subclass.to_s.split('::').last
17
+ full_name = subclass.name
9
18
 
10
- unless Object.const_defined?(name.to_sym)
11
- # We need a unique type for this constant
12
- clz = Class.new(StubPage)
13
- Object.const_set(name.to_sym, clz)
19
+ if full_name == 'IOS' || full_name == 'Android'
20
+ raise "Invalid page name '#{full_name}'"
21
+ end
22
+
23
+ os_scope = full_name.split('::').first
24
+
25
+ if os_scope == 'IOS' || os_scope == 'Android'
26
+ page_name = full_name.split('::', 2).last
27
+
28
+ unless Calabash.is_defined?(page_name)
29
+ scopes = page_name.split('::')
30
+
31
+ previous_scope = ''
32
+
33
+ scopes[0..-2].each do |scope|
34
+ old_scope = Calabash.recursive_const_get("Object::#{os_scope}#{previous_scope}")
35
+ new_scope = Calabash.recursive_const_get("Object#{previous_scope}")
36
+
37
+ old_const = old_scope.const_get(scope.to_sym)
38
+
39
+ if new_scope.const_defined?(scope.to_sym)
40
+ new_scope.send(:remove_const, scope.to_sym)
41
+ end
42
+
43
+ new_scope.const_set(scope.to_sym, old_const.class.allocate)
44
+
45
+ previous_scope << "::#{scope}"
46
+ end
47
+
48
+ simple_page_name = page_name.split('::').last.to_sym
49
+ new_scope = Calabash.recursive_const_get("Object#{previous_scope}")
50
+
51
+ unless new_scope.const_defined?(simple_page_name, false)
52
+ clz = Class.new(StubPage)
53
+ new_scope.const_set(simple_page_name, clz)
54
+ end
55
+ end
14
56
  end
15
57
  end
16
58
 
17
59
  private_class_method :new
18
60
 
61
+ # @!visibility private
19
62
  def initialize(world)
20
63
  @world = world
21
64
  end
22
65
 
66
+ # A query that distinguishes your page.
67
+ # @return [String, Hash, Calabash::Query] A query.
23
68
  def trait
24
69
  raise 'Implement your own trait'
25
70
  end
26
71
 
72
+ # Waits for the page trait to appear.
27
73
  def await(options={})
28
74
  wait_for_view(trait, options)
29
75
  end
@@ -3,6 +3,7 @@ module Calabash
3
3
  module Patch
4
4
  require 'calabash/patch/array'
5
5
 
6
+ # @!visibility private
6
7
  def self.apply_patches!
7
8
  modules = Patch.constants(false)
8
9
 
@@ -4,13 +4,13 @@ module Calabash
4
4
 
5
5
  # @!visibility private
6
6
  module Array
7
- def to_pct
8
- if length != 2
9
- raise RangeError, "Cannot convert #{self} to {:x, :y} hash"
10
- end
11
-
12
- {x: self.[](0), y: self.[](1)}
13
- end
7
+ # def to_pct
8
+ # if length != 2
9
+ # raise RangeError, "Cannot convert #{self} to {:x, :y} hash"
10
+ # end
11
+ #
12
+ # {x: self.[](0), y: self.[](1)}
13
+ # end
14
14
  end
15
15
  end
16
16
  end
@@ -1,8 +1,6 @@
1
1
  module Calabash
2
2
 
3
3
  # A representation of a Calabash query.
4
- # @todo Query needs more documentation.
5
- # @todo Query needs some methods moved to private or doc'd private.
6
4
  class Query
7
5
  # @!visibility private
8
6
  def self.web_query?(query_string)
@@ -110,6 +108,13 @@ module Calabash
110
108
  result
111
109
  end
112
110
 
111
+ # Create a new query. The returned instance will be frozen (immutable).
112
+ #
113
+ # @example
114
+ # Calabash::Query.new({marked: 'mark'})
115
+ # Calabash::Query.new("myview")
116
+ #
117
+ # @param [String, Hash, Calabash::Query] query The query to create
113
118
  def initialize(query)
114
119
  unless query.is_a?(Query) || query.is_a?(Hash) || query.is_a?(String)
115
120
  raise ArgumentError, "Invalid argument for query: '#{query}' (#{query.class})"
@@ -120,6 +125,13 @@ module Calabash
120
125
  freeze
121
126
  end
122
127
 
128
+ # Parse the query to it's string representation.
129
+ #
130
+ # @example
131
+ # puts Calabash::Query.new({marked: 'foo'})
132
+ # # => "* marked:'foo'"
133
+ #
134
+ # @!visibility private
123
135
  def to_s
124
136
  if @query.is_a?(Query)
125
137
  @query.to_s
@@ -130,14 +142,17 @@ module Calabash
130
142
  end
131
143
  end
132
144
 
145
+ # @!visibility private
133
146
  def inspect
134
147
  "<Calabash::Query #{@query.inspect}>"
135
148
  end
136
149
 
150
+ # @!visibility private
137
151
  def web_query?
138
152
  Query.web_query?(to_s)
139
153
  end
140
154
 
155
+ # @!visibility private
141
156
  WEB_QUERY_INDICATORS =
142
157
  [
143
158
  {
@@ -4,6 +4,7 @@ module Calabash
4
4
  # It will, unlike `Array`, raise an IndexError instead of returning nil if
5
5
  # an entry outside bounds is accessed.
6
6
  class QueryResult < Array
7
+ # @!visibility private
7
8
  def self.create(result, query)
8
9
  query_result = QueryResult.send(:new, query)
9
10
  query_result.send(:initialize_copy, result)
@@ -13,36 +14,43 @@ module Calabash
13
14
 
14
15
  private_class_method :new
15
16
 
17
+ # @!visibility private
16
18
  attr_reader :query
17
19
 
20
+ # @!visibility private
18
21
  def initialize(query)
19
22
  @query = Query.new(query)
20
23
  end
21
24
 
25
+ # @!visibility private
22
26
  def first(*several_variants)
23
27
  ensure_in_bounds(0)
24
28
 
25
29
  super(*several_variants)
26
30
  end
27
31
 
32
+ # @!visibility private
28
33
  def last(*several_variants)
29
34
  ensure_in_bounds(-1)
30
35
 
31
36
  super(*several_variants)
32
37
  end
33
38
 
39
+ # @!visibility private
34
40
  def [](index)
35
41
  ensure_in_bounds(index)
36
42
 
37
43
  super(index)
38
44
  end
39
45
 
46
+ # @!visibility private
40
47
  def at(index)
41
48
  ensure_in_bounds(index)
42
49
 
43
50
  super(index)
44
51
  end
45
52
 
53
+ # @!visibility private
46
54
  def fetch(*several_variants)
47
55
  unless block_given? || several_variants.length > 1
48
56
  ensure_in_bounds(several_variants.first)
@@ -51,7 +59,12 @@ module Calabash
51
59
  super(*several_variants)
52
60
  end
53
61
 
62
+ # @!visibility private
54
63
  def ensure_in_bounds(index)
64
+ unless index.is_a?(Numeric)
65
+ return true
66
+ end
67
+
55
68
  if empty?
56
69
  raise IndexError, "Query result is empty"
57
70
  end
@@ -65,6 +78,7 @@ module Calabash
65
78
  end
66
79
  end
67
80
 
81
+ # @!visibility private
68
82
  def self.recursive_freeze(object)
69
83
  if object.is_a?(Array)
70
84
  object.each do |entry|
@@ -4,7 +4,6 @@ require 'pathname'
4
4
  module Calabash
5
5
 
6
6
  # A public API for taking screenshots.
7
- # @!visibility private
8
7
  module Screenshot
9
8
  # @!visibility private
10
9
  def self.screenshot_directory_prefix
@@ -12,6 +11,7 @@ module Calabash
12
11
  end
13
12
 
14
13
  # Set the screenshot directory prefix.
14
+ # @!visibility private
15
15
  def self.screenshot_directory_prefix=(value)
16
16
  if class_variable_defined?(:@@screenshots_taken) &&
17
17
  @@screenshots_taken != 0
@@ -24,22 +24,36 @@ module Calabash
24
24
  # @!visibility private
25
25
  self.screenshot_directory_prefix = 'test_run_'
26
26
 
27
- # Takes a screenshot and saves it. The file is stored in the directory
28
- # given by the ENV variable $CAL_SCREENSHOT_DIR, or by default in
29
- # the relative directory 'screenshots'. The files are saved in a
27
+ # Takes a screenshot and saves it.
28
+ #
29
+ # If `name` is a relative path or a file name, then the file is stored in
30
+ # the directory specified by the ENV variable CAL_SCREENSHOT_DIR, or by
31
+ # default in the relative directory 'screenshots'. The files are saved in a
30
32
  # sub directory named test_run_n, where n is unique and incrementing for
31
33
  # each new test run. The filename of the screenshot will be `name`.
32
34
  # If `name` is not given (nil), the screenshot will be saved as
33
35
  # screenshot_N, where N is the total amount of screenshots taken for the
34
36
  # test run.
35
37
  #
38
+ # If the name given is an absolute path, then Calabash will save the
39
+ # screenshot to the absolute directory given.
40
+ #
41
+ # If the name given starts with ./ (e.g. `screenshot('./foo.png')`) then
42
+ # the filename will be saved relative to the current working directory.
43
+ #
44
+ # If the file specified by `name` has no extension then the filename will
45
+ # default to name + '.png'.
46
+ #
47
+ # If the directories specified do not exist, Calabash will create them.
48
+ #
36
49
  # @param [String] name Name of the screenshot.
37
50
  # @return [String] Path to the screenshot
38
51
  def screenshot(name=nil)
39
52
  Device.default.screenshot(name)
40
53
  end
41
54
 
42
- # Takes a screenshot and embeds it in the test report.
55
+ # Takes a screenshot and embeds it in the test report. This method is only
56
+ # available/useful when running in the context of cucumber.
43
57
  # @see Screenshot#screenshot
44
58
  def screenshot_embed(name=nil)
45
59
  path = screenshot(name)
@@ -58,11 +72,17 @@ module Calabash
58
72
 
59
73
  @@screenshots_taken += 1
60
74
 
61
- unless Dir.exist?(File.expand_path(screenshot_directory))
62
- FileUtils.mkdir_p(File.expand_path(screenshot_directory))
75
+ if name.start_with?('./')
76
+ name = File.join(Dir.pwd, "#{name[2..-1]}")
77
+ end
78
+
79
+ file_name = File.expand_path(name, screenshot_directory)
80
+
81
+ unless Dir.exist?(File.dirname(file_name))
82
+ FileUtils.mkdir_p(File.dirname(file_name))
63
83
  end
64
84
 
65
- File.join(screenshot_directory, name)
85
+ file_name
66
86
  end
67
87
 
68
88
  # @!visibility private
data/lib/calabash/text.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  module Calabash
2
2
 
3
3
  # A public API for entering text.
4
- # @!visibility private
5
4
  module Text
6
5
  # Enter `text` into the currently focused view.
7
6
  #
@@ -15,7 +14,8 @@ module Calabash
15
14
  # @see Calabash::Text#enter_text
16
15
  #
17
16
  # @param [String] text The text to type.
18
- # @param query A query describing the view to enter text into.
17
+ # @param [String, Hash, Calabash::Query] query A query describing the view
18
+ # to enter text into.
19
19
  def enter_text_in(query, text)
20
20
  _enter_text_in(query, text)
21
21
  end
@@ -28,27 +28,110 @@ module Calabash
28
28
  # Clears the text `view`
29
29
  # @see Calabash::Text#clear_text
30
30
  #
31
- # @param query A query describing the view to clear text in.
31
+ # @param [String, Hash, Calabash::Query] query A query describing the view
32
+ # to clear text in.
32
33
  def clear_text_in(query)
33
34
  _clear_text_in(query)
34
35
  end
35
36
 
36
- # @todo add docs
37
- def tap_current_keyboard_action_key
38
- _tap_current_keyboard_action_key
37
+ # Taps the keyboard action key. On iOS there is only one action key, which
38
+ # is the blue coloured key on the standard keyboard. On Android, there can
39
+ # be multiple actions keys available depending on the keyboard, but one key
40
+ # is often replacing the enter key, becoming the default action key. The
41
+ # view in focus on Android asks to keyboard to show one action key, but the
42
+ # keyboard might not adhere to this.
43
+ #
44
+ # On iOS some examples include:
45
+ # * Return
46
+ # * Next
47
+ # * Go
48
+ # * Join
49
+ # * Search
50
+ #
51
+ # On Android some examples include:
52
+ # * Search
53
+ # * Next
54
+ # * Previous
55
+ #
56
+ # See http://developer.android.com/reference/android/view/inputmethod/EditorInfo.html
57
+ #
58
+ # @example
59
+ # tap_keyboard_action_key(:search)
60
+ # tap_keyboard_action_key(:send)
61
+ # tap_keyboard_action_key(:next)
62
+ # tap_keyboard_action_key(:previous)
63
+ #
64
+ # Notice that, for Android, Calabash does not ensure that this particular action key is
65
+ # actually available on the current keyboard.
66
+ #
67
+ # Not all keyboards have an action key. For example, on iOS, numeric keyboards
68
+ # do not have an action key. On Android, if no action key is set for the
69
+ # view, the enter key is pressed instead.
70
+ #
71
+ # @param [Symbol] action_key The action key to press. This is only
72
+ # used for Android.
73
+ # @raise [ArgumentError] If action_key if set for iOS.
74
+ def tap_keyboard_action_key(action_key = nil)
75
+ _tap_keyboard_action_key(action_key)
76
+ true
39
77
  end
40
78
 
41
79
  # Escapes single quotes in `string`.
42
80
  #
43
81
  # @example
44
- # > escape_quotes("Let's get this done.")
82
+ # escape_single_quotes("Let's get this done.")
45
83
  # => "Let\\'s get this done."
84
+ #
85
+ # @example
86
+ # query("* text:'#{escape_single_quotes("Let's go")}'")
87
+ # # Equivalent to
88
+ # query("* text:'Let\\'s go'")
89
+ #
46
90
  # @param [String] string The string to escape.
47
91
  # @return [String] A string with its single quotes properly escaped.
48
92
  def escape_single_quotes(string)
49
93
  Text.escape_single_quotes(string)
50
94
  end
51
95
 
96
+ # Returns true if there is a visible keyboard.
97
+ # On Android, if a physical keyboard is connected, this method will always
98
+ # return true.
99
+ #
100
+ # @return [Boolean] Returns true if there is a visible keyboard.
101
+ def keyboard_visible?
102
+ _keyboard_visible?
103
+ end
104
+
105
+ # Waits for a keyboard to appear.
106
+ #
107
+ # @see Calabash::Wait.default_options
108
+ #
109
+ # @param [Number] timeout How long to wait for the keyboard.
110
+ # @raise [Calabash::Wait::TimeoutError] Raises error if no keyboard
111
+ # appears.
112
+ def wait_for_keyboard(timeout=nil)
113
+ keyboard_timeout = keyboard_wait_timeout(timeout)
114
+ message = "Timed out after #{keyboard_timeout} seconds waiting for the keyboard to appear"
115
+ wait_for(message, timeout: keyboard_timeout) do
116
+ keyboard_visible?
117
+ end
118
+ end
119
+
120
+ # Waits for the keyboard to disappear.
121
+ #
122
+ # @see Calabash::Wait.default_options
123
+ #
124
+ # @param [Number] timeout How log to wait for the keyboard to disappear.
125
+ # @raise [Calabash::Wait::TimeoutError] Raises error if any keyboard is
126
+ # visible after the `timeout`.
127
+ def wait_for_no_keyboard(timeout=nil)
128
+ keyboard_timeout = keyboard_wait_timeout(timeout)
129
+ message = "Timed out after #{keyboard_timeout} seconds waiting for the keyboard to disappear"
130
+ wait_for(message, timeout: keyboard_timeout) do
131
+ !keyboard_visible?
132
+ end
133
+ end
134
+
52
135
  # @!visibility private
53
136
  def _enter_text(text)
54
137
  abstract_method!
@@ -70,7 +153,12 @@ module Calabash
70
153
  end
71
154
 
72
155
  # @!visibility private
73
- def _tap_current_keyboard_action_key
156
+ def _tap_keyboard_action_key(action_key)
157
+ abstract_method!
158
+ end
159
+
160
+ # @!visibility private
161
+ def _keyboard_visible?
74
162
  abstract_method!
75
163
  end
76
164
 
@@ -78,5 +166,14 @@ module Calabash
78
166
  def self.escape_single_quotes(string)
79
167
  string.gsub("'", "\\\\'")
80
168
  end
169
+
170
+ # @!visibility private
171
+ def keyboard_wait_timeout(timeout)
172
+ if timeout.nil?
173
+ Calabash::Gestures::DEFAULT_GESTURE_WAIT_TIMEOUT
174
+ else
175
+ timeout
176
+ end
177
+ end
81
178
  end
82
179
  end