appium_lib 9.4.9 → 9.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/appium_lib.rb CHANGED
@@ -3,7 +3,6 @@ Encoding.default_external = Encoding::UTF_8
3
3
  Encoding.default_internal = Encoding::UTF_8
4
4
 
5
5
  require 'forwardable' unless defined? Forwardable
6
- require_relative 'appium_lib/rails/duplicable'
7
6
 
8
7
  # Init global driver
9
8
  $driver = nil
@@ -35,13 +35,7 @@ module Appium
35
35
  # scoped to: text resource-id content-desc
36
36
  attributes_values = attributes.values
37
37
  strings = $driver.lazy_load_strings
38
- id_matches = []
39
-
40
- unless strings.empty?
41
- id_matches = strings.select do |_key, value|
42
- attributes_values.include? value
43
- end
44
- end
38
+ id_matches = strings.empty? ? [] : strings.select { |_key, value| attributes_values.include? value }
45
39
 
46
40
  string_ids = nil
47
41
 
@@ -1,5 +1,8 @@
1
1
  module Appium
2
2
  module Error
3
3
  class NotSupportedAppiumServer < RuntimeError; end
4
+
5
+ # Server side error
6
+ class ServerError; end
4
7
  end
5
8
  end
@@ -26,16 +26,26 @@ module Appium
26
26
  # execute_script 'mobile: tap', :x => 0.0, :y => 0.98
27
27
  # ```
28
28
  #
29
- # https://github.com/appium/appium/wiki/Automating-mobile-gestures
30
29
  # @return [OpenStruct] the relative x, y in a struct. ex: { x: 0.50, y: 0.20 }
31
30
  def location_rel
32
- location = self.location
33
- location_x = location.x.to_f
34
- location_y = location.y.to_f
31
+ # TODO: Remove with 'refine Appium ruby binding'
32
+ # https://github.com/appium/ruby_lib/issues/602
33
+ if ::Appium.selenium_webdriver_version_more?('3.4.0')
34
+ rect = self.rect
35
+ location_x = rect.x.to_f
36
+ location_y = rect.y.to_f
37
+
38
+ size_width = rect.width.to_f
39
+ size_height = rect.height.to_f
40
+ else
41
+ location = self.location
42
+ location_x = location.x.to_f
43
+ location_y = location.y.to_f
35
44
 
36
- size = self.size
37
- size_width = size.width.to_f
38
- size_height = size.height.to_f
45
+ size = self.size
46
+ size_width = size.width.to_f
47
+ size_height = size.height.to_f
48
+ end
39
49
 
40
50
  center_x = location_x + (size_width / 2.0)
41
51
  center_y = location_y + (size_height / 2.0)
@@ -1,5 +1,5 @@
1
1
  module Appium
2
2
  # Version and Date are defined on the 'Appium' module, not 'Appium::Common'
3
- VERSION = '9.4.9'.freeze unless defined? ::Appium::VERSION
4
- DATE = '2017-07-01'.freeze unless defined? ::Appium::DATE
3
+ VERSION = '9.4.10'.freeze unless defined? ::Appium::VERSION
4
+ DATE = '2017-07-29'.freeze unless defined? ::Appium::DATE
5
5
  end
@@ -248,6 +248,11 @@ module Appium
248
248
  nil # return nil
249
249
  end
250
250
 
251
+ def self.selenium_webdriver_version_more?(version)
252
+ require 'rubygems'
253
+ Gem.loaded_specs['selenium-webdriver'].version >= Gem::Version.new(version)
254
+ end
255
+
251
256
  class Driver
252
257
  module Capabilities
253
258
  # except for browser_name, default capability is equal to ::Selenium::WebDriver::Remote::Capabilities.firefox
@@ -366,30 +371,11 @@ module Appium
366
371
  raise 'opts must be a hash' unless opts.is_a? Hash
367
372
 
368
373
  opts = Appium.symbolize_keys opts
369
-
370
374
  @caps = Capabilities.init_caps_for_appium(opts[:caps] || {})
371
375
 
372
376
  appium_lib_opts = opts[:appium_lib] || {}
373
377
 
374
- # appium_lib specific values
375
- @custom_url = appium_lib_opts.fetch :server_url, false
376
- @export_session = appium_lib_opts.fetch :export_session, false
377
- @default_wait = appium_lib_opts.fetch :wait, 0
378
- @sauce_username = appium_lib_opts.fetch :sauce_username, ENV['SAUCE_USERNAME']
379
- @sauce_username = nil if !@sauce_username || (@sauce_username.is_a?(String) && @sauce_username.empty?)
380
- @sauce_access_key = appium_lib_opts.fetch :sauce_access_key, ENV['SAUCE_ACCESS_KEY']
381
- @sauce_access_key = nil if !@sauce_access_key || (@sauce_access_key.is_a?(String) && @sauce_access_key.empty?)
382
- @sauce_endpoint = appium_lib_opts.fetch :sauce_endpoint, ENV['SAUCE_ENDPOINT']
383
- @sauce_endpoint = 'ondemand.saucelabs.com:443/wd/hub' if
384
- !@sauce_endpoint || (@sauce_endpoint.is_a?(String) && @sauce_endpoint.empty?)
385
- @appium_port = appium_lib_opts.fetch :port, 4723
386
- # timeout and interval used in ::Appium::Comm.wait/wait_true
387
- @appium_wait_timeout = appium_lib_opts.fetch :wait_timeout, 30
388
- @appium_wait_interval = appium_lib_opts.fetch :wait_interval, 0.5
389
-
390
- # to pass it in Selenium.new.
391
- # `listener = opts.delete(:listener)` is called in Selenium::Driver.new
392
- @listener = appium_lib_opts.fetch :listener, nil
378
+ set_appium_lib_specific_values(appium_lib_opts)
393
379
 
394
380
  # Path to the .apk, .app or .app.zip.
395
381
  # The path can be local or remote for Sauce.
@@ -402,6 +388,9 @@ module Appium
402
388
  @appium_device = @appium_device.is_a?(Symbol) ? @appium_device : @appium_device.downcase.strip.intern if @appium_device
403
389
 
404
390
  @automation_name = @caps[:automationName] if @caps[:automationName]
391
+ @automation_name = if @automation_name
392
+ @automation_name.is_a?(Symbol) ? @automation_name : @automation_name.downcase.strip.intern
393
+ end
405
394
 
406
395
  # load common methods
407
396
  extend Appium::Common
@@ -411,7 +400,10 @@ module Appium
411
400
  extend Appium::Android
412
401
  else
413
402
  extend Appium::Ios
414
- extend Appium::Ios::XcuitestGesture if automation_name_is_xcuitest? # Override touch actions
403
+ if automation_name_is_xcuitest? # Override touch actions
404
+ extend Appium::Ios::Xcuitest
405
+ extend Appium::Ios::Xcuitest::Gesture
406
+ end
415
407
  end
416
408
 
417
409
  # apply os specific patches
@@ -437,9 +429,36 @@ module Appium
437
429
  self # return newly created driver
438
430
  end
439
431
 
432
+ private
433
+
434
+ def set_appium_lib_specific_values(appium_lib_opts)
435
+ @custom_url = appium_lib_opts.fetch :server_url, false
436
+ @export_session = appium_lib_opts.fetch :export_session, false
437
+ @default_wait = appium_lib_opts.fetch :wait, 0
438
+
439
+ @sauce_username = appium_lib_opts.fetch :sauce_username, ENV['SAUCE_USERNAME']
440
+ @sauce_username = nil if !@sauce_username || (@sauce_username.is_a?(String) && @sauce_username.empty?)
441
+ @sauce_access_key = appium_lib_opts.fetch :sauce_access_key, ENV['SAUCE_ACCESS_KEY']
442
+ @sauce_access_key = nil if !@sauce_access_key || (@sauce_access_key.is_a?(String) && @sauce_access_key.empty?)
443
+ @sauce_endpoint = appium_lib_opts.fetch :sauce_endpoint, ENV['SAUCE_ENDPOINT']
444
+ @sauce_endpoint = 'ondemand.saucelabs.com:443/wd/hub' if
445
+ !@sauce_endpoint || (@sauce_endpoint.is_a?(String) && @sauce_endpoint.empty?)
446
+
447
+ @appium_port = appium_lib_opts.fetch :port, 4723
448
+ # timeout and interval used in ::Appium::Comm.wait/wait_true
449
+ @appium_wait_timeout = appium_lib_opts.fetch :wait_timeout, 30
450
+ @appium_wait_interval = appium_lib_opts.fetch :wait_interval, 0.5
451
+
452
+ # to pass it in Selenium.new.
453
+ # `listener = opts.delete(:listener)` is called in Selenium::Driver.new
454
+ @listener = appium_lib_opts.fetch :listener, nil
455
+ end
456
+
457
+ public
458
+
440
459
  # Returns a hash of the driver attributes
441
460
  def driver_attributes
442
- attributes = {
461
+ {
443
462
  caps: @caps,
444
463
  automation_name: @automation_name,
445
464
  custom_url: @custom_url,
@@ -455,12 +474,6 @@ module Appium
455
474
  wait_timeout: @appium_wait_timeout,
456
475
  wait_interval: @appium_wait_interval
457
476
  }
458
-
459
- # Return duplicates so attributes are immutable
460
- attributes.each do |key, value|
461
- attributes[key] = value.duplicable? ? value.dup : value
462
- end
463
- attributes
464
477
  end
465
478
 
466
479
  def device_is_android?
@@ -470,13 +483,13 @@ module Appium
470
483
  # Return true if automationName is 'XCUITest'
471
484
  # @return [Boolean]
472
485
  def automation_name_is_xcuitest?
473
- !@automation_name.nil? && 'xcuitest'.casecmp(@automation_name).zero?
486
+ !@automation_name.nil? && @automation_name == :xcuitest
474
487
  end
475
488
 
476
489
  # Return true if automationName is 'uiautomator2'
477
490
  # @return [Boolean]
478
491
  def automation_name_is_uiautomator2?
479
- !@automation_name.nil? && 'uiautomator2'.casecmp(@automation_name).zero?
492
+ !@automation_name.nil? && @automation_name == :uiautomator2
480
493
  end
481
494
 
482
495
  # Return true if the target Appium server is over REQUIRED_VERSION_XCUITEST.
@@ -512,11 +525,11 @@ module Appium
512
525
  def appium_server_version
513
526
  driver.remote_status
514
527
  rescue Selenium::WebDriver::Error::WebDriverError => ex
515
- raise unless ex.message.include?('content-type=""')
528
+ raise ::Appium::Error::ServerError unless ex.message.include?('content-type=""')
516
529
  # server (TestObject for instance) does not respond to status call
517
530
  {}
518
531
  rescue Selenium::WebDriver::Error::ServerError => e
519
- raise unless e.message.include?('status code 500')
532
+ raise ::Appium::Error::ServerError unless e.message.include?('status code 500')
520
533
  # driver.remote_status returns 500 error for using selenium grid
521
534
  {}
522
535
  end
@@ -612,11 +625,33 @@ module Appium
612
625
  end
613
626
 
614
627
  # Creates a new global driver and quits the old one if it exists.
628
+ # You can customise http_client as the following
629
+ #
630
+ # @example
631
+ # ```ruby
632
+ # require 'rubygems'
633
+ # require 'appium_lib'
634
+ #
635
+ # # platformName takes a string or a symbol.
636
+ #
637
+ # # Start iOS driver
638
+ # opts = {
639
+ # caps: {
640
+ # platformName: :ios,
641
+ # app: '/path/to/MyiOS.app'
642
+ # },
643
+ # appium_lib: {
644
+ # wait_timeout: 30
645
+ # }
646
+ # }
647
+ # custom_http_client = Custom::Http::Client.new(opts)
648
+ # Appium::Driver.new(opts).start_driver(custom_http_client)
615
649
  #
616
650
  # @return [Selenium::WebDriver] the new global driver
617
- def start_driver
651
+ def start_driver(http_client =
652
+ Selenium::WebDriver::Remote::Http::Default.new(open_timeout: 999_999, read_timeout: 999_999))
618
653
  # open_timeout and read_timeout are explicit wait.
619
- @http_client ||= Selenium::WebDriver::Remote::Http::Default.new(open_timeout: 999_999, read_timeout: 999_999)
654
+ @http_client ||= http_client
620
655
 
621
656
  begin
622
657
  driver_quit
@@ -631,12 +666,7 @@ module Appium
631
666
  @driver.extend Selenium::WebDriver::DriverExtensions::HasLocation
632
667
 
633
668
  # export session
634
- if @export_session
635
- # rubocop:disable Style/RescueModifier
636
- File.open('/tmp/appium_lib_session', 'w') do |f|
637
- f.puts @driver.session_id
638
- end rescue nil
639
- end
669
+ write_session_id(@driver.session_id) if @export_session
640
670
  rescue Errno::ECONNREFUSED
641
671
  raise "ERROR: Unable to connect to Appium. Is the server running on #{server_url}?"
642
672
  end
@@ -774,6 +804,13 @@ module Appium
774
804
 
775
805
  private
776
806
 
807
+ def write_session_id(session_id)
808
+ File.open('/tmp/appium_lib_session', 'w') { |f| f.puts session_id }
809
+ rescue IOError => e
810
+ ::Appium::Logger.warn e
811
+ nil
812
+ end
813
+
777
814
  # If "automationName" is set only server side, this method set "automationName" attribute into @automation_name.
778
815
  # Since @automation_name is set only client side before start_driver is called.
779
816
  def set_automation_name_if_nil
@@ -16,24 +16,30 @@ module Appium
16
16
  # find_elements :predicate, 'wdName == "Buttons"'
17
17
  # find_elements :predicate, 'wdValue == "SearchBar" AND isWDDivisible == 1'
18
18
  # ```
19
- #
20
- # @!method ios_class_chain_find
21
- # Only for XCUITest(WebDriverAgent)
22
- # find_element/s can be used with a [class chain]( https://github.com/facebook/WebDriverAgent/wiki/Queries)
23
- #
24
- # ```ruby
25
- # # select the third child button of the first child window element
26
- # find_elements :class_chain, 'XCUIElementTypeWindow/XCUIElementTypeButton[3]'
27
- # # select all the children windows
28
- # find_elements :class_chain, 'XCUIElementTypeWindow'
29
- # # select the second last child of the second child window
30
- # find_elements :class_chain, 'XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]'
31
- # ```
32
19
  def extended(_mod)
33
20
  ::Appium::Driver::SearchContext::FINDERS[:uiautomation] = '-ios uiautomation'
34
21
  ::Appium::Driver::SearchContext::FINDERS[:predicate] = '-ios predicate string'
35
- ::Appium::Driver::SearchContext::FINDERS[:class_chain] = '-ios class chain'
36
22
  end
37
23
  end # class << self
24
+
25
+ module Xcuitest
26
+ class << self
27
+ # @!method ios_class_chain_find
28
+ # Only for XCUITest(WebDriverAgent)
29
+ # find_element/s can be used with a [class chain]( https://github.com/facebook/WebDriverAgent/wiki/Queries)
30
+ #
31
+ # ```ruby
32
+ # # select the third child button of the first child window element
33
+ # find_elements :class_chain, 'XCUIElementTypeWindow/XCUIElementTypeButton[3]'
34
+ # # select all the children windows
35
+ # find_elements :class_chain, 'XCUIElementTypeWindow'
36
+ # # select the second last child of the second child window
37
+ # find_elements :class_chain, 'XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]'
38
+ # ```
39
+ def extended(_mod)
40
+ ::Appium::Driver::SearchContext::FINDERS[:class_chain] = '-ios class chain'
41
+ end
42
+ end
43
+ end
38
44
  end # module Ios
39
45
  end # module Appium
@@ -1,184 +1,186 @@
1
1
  module Appium
2
2
  module Ios
3
- module XcuitestGesture
4
- # @param [string] direction Either 'up', 'down', 'left' or 'right'.
5
- # @option opts [Element] :element Element to swipe on
6
- #
7
- # ```ruby
8
- # swipe direction: "down"
9
- # ```
10
- def swipe(direction:, element: nil)
11
- return unless %w(up down left right).include?(direction)
12
-
13
- args = { direction: direction }
14
- args[:element] = element.ref if element
15
-
16
- execute_script 'mobile: swipe', args
17
- end
18
-
19
- # @param [string] direction Either 'up', 'down', 'left' or 'right'.
20
- # @option opts [String] :name the accessibility id of the child element, to which scrolling is performed.
21
- # @option opts [Element] :element Element id to long tap on.
22
- # @option opts [bool] :to_visible Boolean parameter. If set to true then asks to scroll to the first visible
23
- # element in the parent container. Has no effect if element is not set
24
- # @option opts [String] :predicate_string the NSPredicate locator of the child element,
25
- # to which the scrolling should be performed. Has no effect if element is not a container
26
- #
27
- # ```ruby
28
- # scroll direction: "down"
29
- # ```
30
- def scroll(direction:, name: nil, element: nil, to_visible: nil, predicate_string: nil)
31
- return 'Set "up", "down", "left" or "right" for :direction' unless %w(up down left right).include?(direction)
32
-
33
- args = { direction: direction }
34
- args[:element] = element.ref if element
35
- args[:name] = name if name
36
- args[:toVisible] = to_visible if to_visible
37
- args[:predicateString] = predicate_string if predicate_string
38
-
39
- execute_script 'mobile: scroll', args
40
- end
41
-
42
- # @param scale [scale] X tap coordinate of type float. Mandatory parameter
43
- # @param velocity [float] Y tap coordinate of type float. Mandatory parameter
44
- # @option opts [Element] :element Element id to long tap on.
45
- #
46
- # ```ruby
47
- # pinch scale: 0.5, velocity: -1
48
- # ```
49
- def pinch(scale:, velocity: 1.0, element: nil)
50
- return unless automation_name_is_xcuitest?
51
-
52
- args = { scale: scale, velocity: velocity }
53
- args[:element] = element.ref if element
54
-
55
- execute_script 'mobile: pinch', args
56
- end
57
-
58
- # @param x [float] X Screen x tap coordinate of type float. Mandatory parameter only if element is not set
59
- # @param y [float] Y Screen y tap coordinate of type float. Mandatory parameter only if element is not set
60
- # @option opts [Element] :element Element to long tap on.
61
- #
62
- # ```ruby
63
- # double_tap x: 100, y: 100
64
- # double_tap element: find_element(:accessibility_id, "some item")
65
- # ```
66
- def double_tap(x: nil, y: nil, element: nil)
67
- return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
68
- return 'Set x, y or element' if (x.nil? || y.nil?) && element.nil?
69
-
70
- args = element.nil? ? { x: x, y: y } : { element: element.ref }
71
- execute_script 'mobile: doubleTap', args
72
- end
73
-
74
- # @param x [float] Screen x long tap coordinate of type float. Mandatory parameter only if element is not set
75
- # @param y [float] Screen y long tap coordinate of type float. Mandatory parameter only if element is not set
76
- # @param duration [Float] The float duration of press action in seconds. Mandatory parameter
77
- # @option opts [Element] :element The internal element identifier (as hexadecimal hash string) to long tap on
78
- #
79
- # ```ruby
80
- # touch_and_hold x: 100, y: 100
81
- # touch_and_hold x: 100, y: 100, duration: 2.0
82
- # touch_and_hold element: find_element(:accessibility_id, "some item")
83
- # ```
84
- def touch_and_hold(x: nil, y: nil, element: nil, duration: 1.0)
85
- return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
86
- return 'Set x, y or element' if (x.nil? || y.nil?) && element.nil?
87
-
88
- args = element.nil? ? { x: x, y: y } : { element: element.ref }
89
- args[:duration] = duration
90
- execute_script 'mobile: touchAndHold', args
91
- end
92
-
93
- # @param [Element] :element Element to long tap on.
94
- #
95
- # ```ruby
96
- # two_finger_tap element: find_element(:accessibility_id, "some item")
97
- # ```
98
- def two_finger_tap(element:)
99
- return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
100
-
101
- args = { element: element.ref }
102
- execute_script 'mobile: twoFingerTap', args
103
- end
104
-
105
- # @param x [float] X tap coordinate of type float. Mandatory parameter
106
- # @param y [float] Y tap coordinate of type float. Mandatory parameter
107
- # @option opts [Element] :element Element id to long tap on. x and y tap coordinates will be calculated
108
- # relatively to the current element position on the screen if this argument is provided.
109
- # Otherwise they should be calculated relatively to screen borders.
110
- #
111
- # ```ruby
112
- # tap x: 100, y: 100
113
- # tap x: 100, y: 100, element: find_element(:accessibility_id, "some item")
114
- # ```
115
- def tap(x:, y:, element: nil)
116
- return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
117
-
118
- args = { x: x, y: y }
119
- args[:element] = element.ref if element
120
- execute_script 'mobile: tap', args
121
- end
122
-
123
- # rubocop:disable Metrics/ParameterLists
124
- # @param duration [float] Float number of seconds in range [0.5, 60]. How long the tap gesture at starting
125
- # drag point should be before to start dragging. Mandatory parameter
126
- # @param from_x [float] The x coordinate of starting drag point (type float). Mandatory parameter
127
- # @param from_y [float] The y coordinate of starting drag point (type float). Mandatory parameter
128
- # @param to_x [float] The x coordinate of ending drag point (type float). Mandatory parameter
129
- # @param to_y [float] The y coordinate of ending drag point (type float). Mandatory parameter
130
- # @option opts [Element] :element Element id to perform drag on. All the coordinates will be calculated
131
- # relatively this this element position on the screen. Absolute screen coordinates are expected
132
- # if this argument is not set
133
- #
134
- # ```ruby
135
- # drag_from_to_for_duration from_x: 100, from_y: 100, to_x: 150, to_y: 150
136
- # ```
137
- def drag_from_to_for_duration(from_x:, from_y:, to_x:, to_y:, duration: 1.0, element: nil)
138
- return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
139
-
140
- args = { fromX: from_x, fromY: from_y, toX: to_x, toY: to_y, duration: duration }
141
- args[:element] = element.ref if element
142
- execute_script 'mobile: dragFromToForDuration', args
143
- end
144
- # rubocop:enable Metrics/ParameterLists
145
-
146
- # https://github.com/facebook/WebDriverAgent/pull/523
147
- # https://github.com/appium/appium-xcuitest-driver/pull/420
148
- # @param order [String] The order to move picker to. "next" or "previous".
149
- # @param element [Element] Element id to perform select picker wheel on.
150
- # @option opts [Integer] :offset The value in range [0.01, 0.5]. Default is 0.2 in server side.
151
- # https://github.com/facebook/WebDriverAgent/pull/549/files
152
- #
153
- # ```ruby
154
- # select_picker_wheel order: "next", element: find_element(:accessibility_id, "some picker")
155
- # ```
156
- def select_picker_wheel(element:, order:, offset: nil)
157
- return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
158
- return 'Set "next" or "previous" for :order' unless %w(next previous).include?(order)
159
-
160
- args = { element: element.ref, order: order }
161
- args[:offset] = offset if offset
162
- execute_script 'mobile: selectPickerWheelValue', args
163
- end
164
-
165
- # @param action [String] The following actions are supported: accept, dismiss and getButtons. Mandatory parameter
166
- # @param button_label [String] The label text of an existing alert button to click on.
167
- # This is an optional parameter and is only valid in combination with accept and dismiss actions.
168
- # @return {} or Selenium::WebDriver::Error::NoSuchAlertError if no action sheet or alert
169
- # or button labels if action is equal to getButtons.
170
- #
171
- # ```ruby
172
- # alert action: "accept"
173
- # alert action: "dismiss"
174
- # ```
175
- def alert(action:, button_label: nil)
176
- return 'Set "accept", "dismiss" or "getButtons" for :action' unless %w(accept dismiss getButtons).include?(action)
177
-
178
- args = { action: action }
179
- args[:button_label] if button_label
180
- execute_script 'mobile: alert', args
181
- end
182
- end # module XcuitestGesture
3
+ module Xcuitest
4
+ module Gesture
5
+ # @param [string] direction Either 'up', 'down', 'left' or 'right'.
6
+ # @option opts [Element] :element Element to swipe on
7
+ #
8
+ # ```ruby
9
+ # swipe direction: "down"
10
+ # ```
11
+ def swipe(direction:, element: nil)
12
+ return unless %w(up down left right).include?(direction)
13
+
14
+ args = { direction: direction }
15
+ args[:element] = element.ref if element
16
+
17
+ execute_script 'mobile: swipe', args
18
+ end
19
+
20
+ # @param [string] direction Either 'up', 'down', 'left' or 'right'.
21
+ # @option opts [String] :name the accessibility id of the child element, to which scrolling is performed.
22
+ # @option opts [Element] :element Element id to long tap on.
23
+ # @option opts [bool] :to_visible Boolean parameter. If set to true then asks to scroll to the first visible
24
+ # element in the parent container. Has no effect if element is not set
25
+ # @option opts [String] :predicate_string the NSPredicate locator of the child element,
26
+ # to which the scrolling should be performed. Has no effect if element is not a container
27
+ #
28
+ # ```ruby
29
+ # scroll direction: "down"
30
+ # ```
31
+ def scroll(direction:, name: nil, element: nil, to_visible: nil, predicate_string: nil)
32
+ return 'Set "up", "down", "left" or "right" for :direction' unless %w(up down left right).include?(direction)
33
+
34
+ args = { direction: direction }
35
+ args[:element] = element.ref if element
36
+ args[:name] = name if name
37
+ args[:toVisible] = to_visible if to_visible
38
+ args[:predicateString] = predicate_string if predicate_string
39
+
40
+ execute_script 'mobile: scroll', args
41
+ end
42
+
43
+ # @param scale [scale] X tap coordinate of type float. Mandatory parameter
44
+ # @param velocity [float] Y tap coordinate of type float. Mandatory parameter
45
+ # @option opts [Element] :element Element id to long tap on.
46
+ #
47
+ # ```ruby
48
+ # pinch scale: 0.5, velocity: -1
49
+ # ```
50
+ def pinch(scale:, velocity: 1.0, element: nil)
51
+ return unless automation_name_is_xcuitest?
52
+
53
+ args = { scale: scale, velocity: velocity }
54
+ args[:element] = element.ref if element
55
+
56
+ execute_script 'mobile: pinch', args
57
+ end
58
+
59
+ # @param x [float] X Screen x tap coordinate of type float. Mandatory parameter only if element is not set
60
+ # @param y [float] Y Screen y tap coordinate of type float. Mandatory parameter only if element is not set
61
+ # @option opts [Element] :element Element to long tap on.
62
+ #
63
+ # ```ruby
64
+ # double_tap x: 100, y: 100
65
+ # double_tap element: find_element(:accessibility_id, "some item")
66
+ # ```
67
+ def double_tap(x: nil, y: nil, element: nil)
68
+ return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
69
+ return 'Set x, y or element' if (x.nil? || y.nil?) && element.nil?
70
+
71
+ args = element.nil? ? { x: x, y: y } : { element: element.ref }
72
+ execute_script 'mobile: doubleTap', args
73
+ end
74
+
75
+ # @param x [float] Screen x long tap coordinate of type float. Mandatory parameter only if element is not set
76
+ # @param y [float] Screen y long tap coordinate of type float. Mandatory parameter only if element is not set
77
+ # @param duration [Float] The float duration of press action in seconds. Mandatory parameter
78
+ # @option opts [Element] :element The internal element identifier (as hexadecimal hash string) to long tap on
79
+ #
80
+ # ```ruby
81
+ # touch_and_hold x: 100, y: 100
82
+ # touch_and_hold x: 100, y: 100, duration: 2.0
83
+ # touch_and_hold element: find_element(:accessibility_id, "some item")
84
+ # ```
85
+ def touch_and_hold(x: nil, y: nil, element: nil, duration: 1.0)
86
+ return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
87
+ return 'Set x, y or element' if (x.nil? || y.nil?) && element.nil?
88
+
89
+ args = element.nil? ? { x: x, y: y } : { element: element.ref }
90
+ args[:duration] = duration
91
+ execute_script 'mobile: touchAndHold', args
92
+ end
93
+
94
+ # @param [Element] :element Element to long tap on.
95
+ #
96
+ # ```ruby
97
+ # two_finger_tap element: find_element(:accessibility_id, "some item")
98
+ # ```
99
+ def two_finger_tap(element:)
100
+ return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
101
+
102
+ args = { element: element.ref }
103
+ execute_script 'mobile: twoFingerTap', args
104
+ end
105
+
106
+ # @param x [float] X tap coordinate of type float. Mandatory parameter
107
+ # @param y [float] Y tap coordinate of type float. Mandatory parameter
108
+ # @option opts [Element] :element Element id to long tap on. x and y tap coordinates will be calculated
109
+ # relatively to the current element position on the screen if this argument is provided.
110
+ # Otherwise they should be calculated relatively to screen borders.
111
+ #
112
+ # ```ruby
113
+ # tap x: 100, y: 100
114
+ # tap x: 100, y: 100, element: find_element(:accessibility_id, "some item")
115
+ # ```
116
+ def tap(x:, y:, element: nil)
117
+ return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
118
+
119
+ args = { x: x, y: y }
120
+ args[:element] = element.ref if element
121
+ execute_script 'mobile: tap', args
122
+ end
123
+
124
+ # rubocop:disable Metrics/ParameterLists
125
+ # @param duration [float] Float number of seconds in range [0.5, 60]. How long the tap gesture at starting
126
+ # drag point should be before to start dragging. Mandatory parameter
127
+ # @param from_x [float] The x coordinate of starting drag point (type float). Mandatory parameter
128
+ # @param from_y [float] The y coordinate of starting drag point (type float). Mandatory parameter
129
+ # @param to_x [float] The x coordinate of ending drag point (type float). Mandatory parameter
130
+ # @param to_y [float] The y coordinate of ending drag point (type float). Mandatory parameter
131
+ # @option opts [Element] :element Element id to perform drag on. All the coordinates will be calculated
132
+ # relatively this this element position on the screen. Absolute screen coordinates are expected
133
+ # if this argument is not set
134
+ #
135
+ # ```ruby
136
+ # drag_from_to_for_duration from_x: 100, from_y: 100, to_x: 150, to_y: 150
137
+ # ```
138
+ def drag_from_to_for_duration(from_x:, from_y:, to_x:, to_y:, duration: 1.0, element: nil)
139
+ return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
140
+
141
+ args = { fromX: from_x, fromY: from_y, toX: to_x, toY: to_y, duration: duration }
142
+ args[:element] = element.ref if element
143
+ execute_script 'mobile: dragFromToForDuration', args
144
+ end
145
+ # rubocop:enable Metrics/ParameterLists
146
+
147
+ # https://github.com/facebook/WebDriverAgent/pull/523
148
+ # https://github.com/appium/appium-xcuitest-driver/pull/420
149
+ # @param order [String] The order to move picker to. "next" or "previous".
150
+ # @param element [Element] Element id to perform select picker wheel on.
151
+ # @option opts [Integer] :offset The value in range [0.01, 0.5]. Default is 0.2 in server side.
152
+ # https://github.com/facebook/WebDriverAgent/pull/549/files
153
+ #
154
+ # ```ruby
155
+ # select_picker_wheel order: "next", element: find_element(:accessibility_id, "some picker")
156
+ # ```
157
+ def select_picker_wheel(element:, order:, offset: nil)
158
+ return 'require XCUITest(WDA)' unless automation_name_is_xcuitest?
159
+ return 'Set "next" or "previous" for :order' unless %w(next previous).include?(order)
160
+
161
+ args = { element: element.ref, order: order }
162
+ args[:offset] = offset if offset
163
+ execute_script 'mobile: selectPickerWheelValue', args
164
+ end
165
+
166
+ # @param action [String] The following actions are supported: accept, dismiss and getButtons. Mandatory parameter
167
+ # @param button_label [String] The label text of an existing alert button to click on.
168
+ # This is an optional parameter and is only valid in combination with accept and dismiss actions.
169
+ # @return {} or Selenium::WebDriver::Error::NoSuchAlertError if no action sheet or alert
170
+ # or button labels if action is equal to getButtons.
171
+ #
172
+ # ```ruby
173
+ # alert action: "accept"
174
+ # alert action: "dismiss"
175
+ # ```
176
+ def alert(action:, button_label: nil)
177
+ return 'Set "accept", "dismiss" or "getButtons" for :action' unless %w(accept dismiss getButtons).include?(action)
178
+
179
+ args = { action: action }
180
+ args[:button_label] if button_label
181
+ execute_script 'mobile: alert', args
182
+ end
183
+ end # module Gesture
184
+ end # module Xcuitest
183
185
  end # module Ios
184
186
  end # module Appium