selenium-webdriver 3.142.7 → 4.0.0.alpha1

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +21 -43
  3. data/lib/selenium/webdriver.rb +2 -4
  4. data/lib/selenium/webdriver/chrome/bridge.rb +3 -21
  5. data/lib/selenium/webdriver/chrome/driver.rb +12 -39
  6. data/lib/selenium/webdriver/chrome/options.rb +3 -7
  7. data/lib/selenium/webdriver/chrome/profile.rb +2 -2
  8. data/lib/selenium/webdriver/chrome/service.rb +4 -9
  9. data/lib/selenium/webdriver/common.rb +7 -16
  10. data/lib/selenium/webdriver/common/action_builder.rb +97 -249
  11. data/lib/selenium/webdriver/common/driver.rb +2 -4
  12. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +1 -1
  13. data/lib/selenium/webdriver/common/element.rb +3 -6
  14. data/lib/selenium/webdriver/common/error.rb +27 -203
  15. data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -5
  16. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +13 -13
  17. data/lib/selenium/webdriver/common/manager.rb +1 -1
  18. data/lib/selenium/webdriver/common/options.rb +148 -24
  19. data/lib/selenium/webdriver/common/service.rb +16 -34
  20. data/lib/selenium/webdriver/common/socket_poller.rb +2 -2
  21. data/lib/selenium/webdriver/common/w3c_options.rb +45 -0
  22. data/lib/selenium/webdriver/edge.rb +0 -1
  23. data/lib/selenium/webdriver/edge/driver.rb +14 -10
  24. data/lib/selenium/webdriver/edge/service.rb +6 -7
  25. data/lib/selenium/webdriver/firefox.rb +2 -6
  26. data/lib/selenium/webdriver/firefox/binary.rb +3 -80
  27. data/lib/selenium/webdriver/firefox/bridge.rb +47 -0
  28. data/lib/selenium/webdriver/firefox/driver.rb +44 -22
  29. data/lib/selenium/webdriver/firefox/marionette/driver.rb +1 -1
  30. data/lib/selenium/webdriver/firefox/options.rb +2 -2
  31. data/lib/selenium/webdriver/firefox/profile.rb +25 -14
  32. data/lib/selenium/webdriver/firefox/service.rb +4 -4
  33. data/lib/selenium/webdriver/ie/driver.rb +5 -18
  34. data/lib/selenium/webdriver/ie/options.rb +2 -2
  35. data/lib/selenium/webdriver/ie/service.rb +4 -4
  36. data/lib/selenium/webdriver/remote.rb +2 -6
  37. data/lib/selenium/webdriver/remote/bridge.rb +515 -69
  38. data/lib/selenium/webdriver/remote/capabilities.rb +77 -99
  39. data/lib/selenium/webdriver/remote/commands.rb +156 -0
  40. data/lib/selenium/webdriver/remote/driver.rb +12 -5
  41. data/lib/selenium/webdriver/remote/http/default.rb +0 -9
  42. data/lib/selenium/webdriver/remote/response.rb +16 -47
  43. data/lib/selenium/webdriver/safari.rb +1 -1
  44. data/lib/selenium/webdriver/safari/bridge.rb +3 -3
  45. data/lib/selenium/webdriver/safari/driver.rb +4 -1
  46. data/lib/selenium/webdriver/safari/service.rb +4 -4
  47. data/lib/selenium/webdriver/support/select.rb +1 -1
  48. data/lib/selenium/webdriver/version.rb +1 -1
  49. data/selenium-webdriver.gemspec +3 -3
  50. metadata +14 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c9f40e154da198fff9e277f0613042c22fff6c98
4
- data.tar.gz: 33cc734e78b08e66c00a4c73f51c635d97c86fe6
3
+ metadata.gz: bc17a0dfe5aefbf024e5e6c64082ba16209c7f36
4
+ data.tar.gz: 6e7cda3f69ed3fcaaa591b9720b7674a3f8d7a23
5
5
  SHA512:
6
- metadata.gz: b40eb2e74a284b21ee31924f7cf20b878c7ab6ebcfe7848b21ad163165c80467c9d3a6fa6252ca74680b82db7186e8c1b73a2ac5f4d37501b0dfa16763ec19b9
7
- data.tar.gz: bc9e9e0128888f3efe33f59160b677cc88655bf986877c534053adf915f3b39a78ff1d88e9b2d80753c1aba49af92f1796bb4f774ca68114027594813cc26ad3
6
+ metadata.gz: 2dfdf006f048202deedb179a6279c5000082e298ecc29c97806dc1199517bfa625ee45d6be2a2f88ece82869a5b4f593795b3940aa781539740a91d2a8a33523
7
+ data.tar.gz: 85a754811a838367f4a93699b1e44b58eb3493a0ec44d9367d7be566a28ec29a820f89befda4d7f73383443d999f87d76f4cc0b484b920600c30d2b15289e496
data/CHANGES CHANGED
@@ -1,53 +1,31 @@
1
- 3.142.7 (2019-12-27)
2
- ====================
3
-
4
- Ruby:
5
- * Fix keyword argument deprecations in Ruby 2.7 (thanks @connorshea)
6
-
7
- 3.142.6 (2019-10-04)
8
- ====================
9
-
10
- Ruby:
11
- * Loosen ChildProcess dependency so that 3.0+ can be used (thanks @jaredbeck)
12
-
13
- 3.142.5 (2019-10-01)
14
- ====================
1
+ 4.0.0.alpha1 (unreleased)
2
+ =========================
15
3
 
16
4
  Ruby:
17
- * Loosen RubyZip dependency so that 1.3+ can be used (thanks @vtamara)
18
-
19
- 3.142.4 (2019-09-02)
20
- ====================
21
-
22
- Chrome:
23
- * Added support for new command for getting logs in ChromeDriver 76+
24
- with W3C mode on
25
-
26
- 3.142.3 (2019-05-21)
27
- ====================
28
-
29
- Firefox:
30
- * Fixed a regression when Firefox binary path was not sent to GeckoDriver
31
- by default and browser could not be located (issue #7219)
32
-
33
-
34
- 3.142.2 (2019-05-11)
35
- ====================
5
+ * Removed Mouse and Keyboard as their implementations are not compliant to WebDriver specification
6
+ * Removed TouchActionBuilder as its implementation is not compliant to WebDriver specification
7
+ * Change ActionBuilder to be fully compliant to WebDriver specification
8
+ * Change Manager to be fully compliant to WebDriver specification
9
+ * Removed all deprecated errors that are not compliant to WebDriver specification
10
+ * Update minimum required Ruby version to 2.4
11
+ * Changed capabilities to use only those that are compliant to WebDriver specification
12
+ * Removed deprecated timeout setter on default HTTP client
13
+ * Removed deprecated Remote::W3C::Capabilities
14
+ * Fixed an issue when services are not shutdown properly
36
15
 
37
16
  Chrome:
38
- * Fixed an issue when getting/setting network conditions and sending CDP
39
- commands didn't work with Grid (issue #7174)
17
+ * Removed support for OSS mode - use W3C mode instead by using
18
+ Selenium::WebDriver::Chrome::Options.new(options: {w3c: true})
40
19
 
41
- Safari:
42
- * Fixed an issue when getting/setting permissions and attaching debugger
43
- didn't work with Grid (issue #7174)
44
-
45
- 3.142.1 (2019-05-07)
46
- ====================
20
+ PhantomJS:
21
+ * Removed support for PhantomJS driver
47
22
 
48
23
  Firefox:
49
- * Fixed an issue when processing error in legacy driver would result
50
- in NoMethodError (issue #7178)
24
+ * Removed support for legacy Firefox driver - use GeckoDriver instead
25
+ * Removed deprecated outdated capabilities
26
+ * Fixed an issue when passing :profile string to Firefox::Options.new would
27
+ result in NoMethodError. Now it will find a profile with such name on your
28
+ system and use it accordingly (issue #7119)
51
29
 
52
30
  3.142.0 (2019-04-24)
53
31
  ====================
@@ -39,7 +39,6 @@ module Selenium
39
39
  autoload :Edge, 'selenium/webdriver/edge'
40
40
  autoload :Firefox, 'selenium/webdriver/firefox'
41
41
  autoload :IE, 'selenium/webdriver/ie'
42
- autoload :PhantomJS, 'selenium/webdriver/phantomjs'
43
42
  autoload :Remote, 'selenium/webdriver/remote'
44
43
  autoload :Safari, 'selenium/webdriver/safari'
45
44
  autoload :Support, 'selenium/webdriver/support'
@@ -54,10 +53,10 @@ module Selenium
54
53
  # Create a new Driver instance with the correct bridge for the given browser
55
54
  #
56
55
  # @overload for(browser)
57
- # @param [:ie, :internet_explorer, :edge, :remote, :chrome, :firefox, :ff, :phantomjs, :safari] browser The browser to
56
+ # @param [:ie, :internet_explorer, :edge, :remote, :chrome, :firefox, :ff, :safari] browser The browser to
58
57
  # create the driver for
59
58
  # @overload for(browser, opts)
60
- # @param [:ie, :internet_explorer, :edge, :remote, :chrome, :firefox, :ff, :phantomjs, :safari] browser The browser to
59
+ # @param [:ie, :internet_explorer, :edge, :remote, :chrome, :firefox, :ff, :safari] browser The browser to
61
60
  # create the driver for
62
61
  # @param [Hash] opts Options passed to Driver.new
63
62
  #
@@ -68,7 +67,6 @@ module Selenium
68
67
  # @see Selenium::WebDriver::IE::Driver
69
68
  # @see Selenium::WebDriver::Edge::Driver
70
69
  # @see Selenium::WebDriver::Chrome::Driver
71
- # @see Selenium::WebDriver::PhantomJS::Driver
72
70
  # @see Selenium::WebDriver::Safari::Driver
73
71
  #
74
72
  # @example
@@ -23,11 +23,9 @@ module Selenium
23
23
  module Bridge
24
24
 
25
25
  COMMANDS = {
26
- get_network_conditions: [:get, 'session/:session_id/chromium/network_conditions'],
27
- set_network_conditions: [:post, 'session/:session_id/chromium/network_conditions'],
28
- send_command: [:post, 'session/:session_id/goog/cdp/execute'],
29
- get_available_log_types: [:get, 'session/:session_id/se/log/types'],
30
- get_log: [:post, 'session/:session_id/se/log']
26
+ get_network_conditions: [:get, '/session/:session_id/chromium/network_conditions'],
27
+ set_network_conditions: [:post, '/session/:session_id/chromium/network_conditions'],
28
+ send_command: [:post, '/session/:session_id/goog/cdp/execute']
31
29
  }.freeze
32
30
 
33
31
  def commands(command)
@@ -46,22 +44,6 @@ module Selenium
46
44
  execute :set_network_conditions, {}, {network_conditions: conditions}
47
45
  end
48
46
 
49
- def available_log_types
50
- types = execute :get_available_log_types
51
- Array(types).map(&:to_sym)
52
- end
53
-
54
- def log(type)
55
- data = execute :get_log, {}, {type: type.to_s}
56
-
57
- Array(data).map do |l|
58
- begin
59
- LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message')
60
- rescue KeyError
61
- next
62
- end
63
- end
64
- end
65
47
  end # Bridge
66
48
  end # Chrome
67
49
  end # WebDriver
@@ -28,7 +28,6 @@ module Selenium
28
28
 
29
29
  class Driver < WebDriver::Driver
30
30
  include DriverExtensions::HasNetworkConditions
31
- include DriverExtensions::HasTouchScreen
32
31
  include DriverExtensions::HasWebStorage
33
32
  include DriverExtensions::HasLocation
34
33
  include DriverExtensions::TakesScreenshot
@@ -40,8 +39,11 @@ module Selenium
40
39
  opts[:url] ||= service_url(opts)
41
40
 
42
41
  listener = opts.delete(:listener)
43
- @bridge = Remote::Bridge.handshake(**opts)
42
+ desired_capabilities = opts.delete(:desired_capabilities)
43
+
44
+ @bridge = Remote::Bridge.new(opts)
44
45
  @bridge.extend Bridge
46
+ @bridge.create_session(desired_capabilities)
45
47
 
46
48
  super(@bridge, listener: listener)
47
49
  end
@@ -66,58 +68,29 @@ module Selenium
66
68
  caps = opts.delete(:desired_capabilities) { Remote::Capabilities.chrome }
67
69
  options = opts.delete(:options) { Options.new }
68
70
 
69
- args = opts.delete(:args) || opts.delete(:switches)
70
- if args
71
- WebDriver.logger.deprecate ':args or :switches', 'Selenium::WebDriver::Chrome::Options#add_argument'
72
- raise ArgumentError, ':args must be an Array of Strings' unless args.is_a? Array
73
-
74
- args.each { |arg| options.add_argument(arg.to_s) }
75
- end
76
-
77
71
  profile = opts.delete(:profile)
78
72
  if profile
79
- WebDriver.logger.deprecate 'Selenium::WebDriver::Chrome::Driver#new with `:profile` parameter',
80
- 'Selenium::WebDriver::Chrome::Options#profile or Options#add_option'
81
-
82
73
  profile = profile.as_json
83
74
 
84
- if options.args.none?(&/user-data-dir/.method(:match?))
85
- options.add_argument("--user-data-dir=#{profile['directory']}")
86
- end
75
+ options.add_argument("--user-data-dir=#{profile[:directory]}") if options.args.none?(&/user-data-dir/.method(:match?))
87
76
 
88
- if profile['extensions']
89
- WebDriver.logger.deprecate 'Selenium::WebDriver::Chrome::Profile#extensions',
77
+ if profile[:extensions]
78
+ WebDriver.logger.deprecate 'Using Selenium::WebDriver::Chrome::Profile#extensions',
90
79
  'Selenium::WebDriver::Chrome::Options#add_extension'
91
- profile['extensions'].each do |extension|
80
+ profile[:extensions].each do |extension|
92
81
  options.add_encoded_extension(extension)
93
82
  end
94
83
  end
95
84
  end
96
85
 
97
- if opts.key?(:detach)
98
- WebDriver.logger.deprecate 'Selenium::WebDriver::Chrome::Driver#new with `:detach` parameter',
99
- 'Selenium::WebDriver::Chrome::Options#new or Options#add_option'
100
- options.add_option(:detach, opts.delete(:detach))
101
- end
102
-
103
- prefs = opts.delete(:prefs)
104
- if prefs
105
- WebDriver.logger.deprecate ':prefs', 'Selenium::WebDriver::Chrome::Options#add_preference'
106
- prefs.each do |key, value|
107
- options.add_preference(key, value)
108
- end
109
- end
86
+ detach = opts.delete(:detach)
87
+ options.add_option(:detach, true) if detach
110
88
 
111
89
  options = options.as_json
112
90
  caps.merge!(options) unless options[Options::KEY].empty?
113
91
 
114
- if opts.key?(:proxy) || opts.key?('proxy')
115
- WebDriver.logger.deprecate 'Selenium::WebDriver::Chrome::Driver#new with `:proxy` parameter',
116
- 'Selenium::WebDriver::Chrome::Capabilities#proxy='
117
-
118
- caps[:proxy] = opts.delete(:proxy) if opts.key?(:proxy)
119
- caps[:proxy] ||= opts.delete('proxy') if opts.key?('proxy')
120
- end
92
+ caps[:proxy] = opts.delete(:proxy) if opts.key?(:proxy)
93
+ caps[:proxy] ||= opts.delete('proxy') if opts.key?('proxy')
121
94
 
122
95
  caps
123
96
  end
@@ -20,9 +20,9 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Chrome
23
- class Options < WebDriver::Common::Options
23
+ class Options
24
24
  attr_reader :args, :prefs, :options, :emulation, :extensions, :encoded_extensions
25
- attr_accessor :binary, :profile, :detach
25
+ attr_accessor :binary
26
26
 
27
27
  KEY = 'goog:chromeOptions'
28
28
 
@@ -49,8 +49,6 @@ module Selenium
49
49
  @extensions = opts.delete(:extensions) || []
50
50
  @options = opts.delete(:options) || {}
51
51
  @emulation = opts.delete(:emulation) || {}
52
- @detach = opts.delete(:detach)
53
- @profile = opts.delete(:profile)
54
52
  @encoded_extensions = []
55
53
  end
56
54
 
@@ -172,7 +170,6 @@ module Selenium
172
170
  File.open(crx_path, 'rb') { |crx_file| Base64.strict_encode64 crx_file.read }
173
171
  end
174
172
  extensions.concat(@encoded_extensions)
175
- add_argument("--user-data-dir=#{@profile[:directory]}") if @profile
176
173
 
177
174
  opts = @options
178
175
  opts[:binary] = @binary if @binary
@@ -180,9 +177,8 @@ module Selenium
180
177
  opts[:extensions] = extensions if extensions.any?
181
178
  opts[:mobileEmulation] = @emulation unless @emulation.empty?
182
179
  opts[:prefs] = @prefs unless @prefs.empty?
183
- opts[:detach] = @detach if !@detach.nil? && @detach != false
184
180
 
185
- {KEY => generate_as_json(opts)}
181
+ {KEY => opts}
186
182
  end
187
183
  end # Options
188
184
  end # Chrome
@@ -77,8 +77,8 @@ module Selenium
77
77
 
78
78
  extensions.concat(@encoded_extensions)
79
79
 
80
- opts = {'directory' => directory || layout_on_disk}
81
- opts['extensions'] = extensions if extensions.any?
80
+ opts = {directory: directory || layout_on_disk}
81
+ opts[:extensions] = extensions if extensions.any?
82
82
  opts
83
83
  end
84
84
 
@@ -25,19 +25,14 @@ module Selenium
25
25
  #
26
26
 
27
27
  class Service < WebDriver::Service
28
- @default_port = 9515
29
- @executable = 'chromedriver'
30
- @missing_text = <<~ERROR
28
+ DEFAULT_PORT = 9515
29
+ EXECUTABLE = 'chromedriver'
30
+ MISSING_TEXT = <<~ERROR
31
31
  Unable to find chromedriver. Please download the server from
32
32
  https://chromedriver.storage.googleapis.com/index.html and place it somewhere on your PATH.
33
33
  More info at https://github.com/SeleniumHQ/selenium/wiki/ChromeDriver.
34
34
  ERROR
35
- @shutdown_supported = true
36
-
37
- def self.driver_path=(path)
38
- Platform.assert_executable path if path.is_a?(String)
39
- @driver_path = path
40
- end
35
+ SHUTDOWN_SUPPORTED = true
41
36
 
42
37
  private
43
38
 
@@ -29,9 +29,6 @@ require 'selenium/webdriver/common/port_prober'
29
29
  require 'selenium/webdriver/common/zipper'
30
30
  require 'selenium/webdriver/common/wait'
31
31
  require 'selenium/webdriver/common/alert'
32
- require 'selenium/webdriver/common/mouse'
33
- require 'selenium/webdriver/common/keyboard'
34
- require 'selenium/webdriver/common/touch_screen'
35
32
  require 'selenium/webdriver/common/target_locator'
36
33
  require 'selenium/webdriver/common/navigation'
37
34
  require 'selenium/webdriver/common/timeouts'
@@ -39,13 +36,16 @@ require 'selenium/webdriver/common/window'
39
36
  require 'selenium/webdriver/common/logger'
40
37
  require 'selenium/webdriver/common/logs'
41
38
  require 'selenium/webdriver/common/manager'
42
- require 'selenium/webdriver/common/w3c_manager'
43
39
  require 'selenium/webdriver/common/search_context'
44
- require 'selenium/webdriver/common/action_builder'
45
40
  require 'selenium/webdriver/common/interactions/key_actions'
46
41
  require 'selenium/webdriver/common/interactions/pointer_actions'
47
- require 'selenium/webdriver/common/w3c_action_builder'
48
- require 'selenium/webdriver/common/touch_action_builder'
42
+ require 'selenium/webdriver/common/interactions/interactions'
43
+ require 'selenium/webdriver/common/interactions/input_device'
44
+ require 'selenium/webdriver/common/interactions/interaction'
45
+ require 'selenium/webdriver/common/interactions/none_input'
46
+ require 'selenium/webdriver/common/interactions/key_input'
47
+ require 'selenium/webdriver/common/interactions/pointer_input'
48
+ require 'selenium/webdriver/common/action_builder'
49
49
  require 'selenium/webdriver/common/html5/shared_web_storage'
50
50
  require 'selenium/webdriver/common/html5/local_storage'
51
51
  require 'selenium/webdriver/common/html5/session_storage'
@@ -55,7 +55,6 @@ require 'selenium/webdriver/common/driver_extensions/has_web_storage'
55
55
  require 'selenium/webdriver/common/driver_extensions/downloads_files'
56
56
  require 'selenium/webdriver/common/driver_extensions/has_location'
57
57
  require 'selenium/webdriver/common/driver_extensions/has_session_id'
58
- require 'selenium/webdriver/common/driver_extensions/has_touch_screen'
59
58
  require 'selenium/webdriver/common/driver_extensions/has_remote_status'
60
59
  require 'selenium/webdriver/common/driver_extensions/has_network_conditions'
61
60
  require 'selenium/webdriver/common/driver_extensions/has_network_connection'
@@ -63,15 +62,7 @@ require 'selenium/webdriver/common/driver_extensions/has_permissions'
63
62
  require 'selenium/webdriver/common/driver_extensions/has_debugger'
64
63
  require 'selenium/webdriver/common/driver_extensions/uploads_files'
65
64
  require 'selenium/webdriver/common/driver_extensions/has_addons'
66
- require 'selenium/webdriver/common/interactions/interactions'
67
- require 'selenium/webdriver/common/interactions/input_device'
68
- require 'selenium/webdriver/common/interactions/interaction'
69
- require 'selenium/webdriver/common/interactions/none_input'
70
- require 'selenium/webdriver/common/interactions/key_input'
71
- require 'selenium/webdriver/common/interactions/pointer_input'
72
65
  require 'selenium/webdriver/common/keys'
73
- require 'selenium/webdriver/common/bridge_helper'
74
66
  require 'selenium/webdriver/common/profile_helper'
75
- require 'selenium/webdriver/common/options'
76
67
  require 'selenium/webdriver/common/driver'
77
68
  require 'selenium/webdriver/common/element'
@@ -19,346 +19,194 @@
19
19
 
20
20
  module Selenium
21
21
  module WebDriver
22
- #
23
- # The ActionBuilder provides the user a way to set up and perform
24
- # complex user interactions.
25
- #
26
- # This class should not be instantiated directly, but is created by Driver#action
27
- #
28
- # @example
29
- #
30
- # driver.action.key_down(:shift).
31
- # click(element).
32
- # click(second_element).
33
- # key_up(:shift).
34
- # drag_and_drop(element, third_element).
35
- # perform
36
- #
37
-
38
22
  class ActionBuilder
39
- #
40
- # @api private
41
- #
23
+ include KeyActions # Actions specific to key inputs
24
+ include PointerActions # Actions specific to pointer inputs
42
25
 
43
- def initialize(mouse, keyboard)
44
- @devices = {
45
- mouse: mouse,
46
- keyboard: keyboard
47
- }
48
-
49
- @actions = []
50
- end
26
+ attr_reader :devices
51
27
 
52
28
  #
53
- # Performs a modifier key press. Does not release
54
- # the modifier key - subsequent interactions may assume it's kept pressed.
55
- # Note that the modifier key is never released implicitly - either
56
- # #key_up(key) or #send_keys(:null) must be called to release the modifier.
29
+ # Initialize a W3C Action Builder. Differs from previous by requiring a bridge and allowing asynchronous actions.
30
+ # The W3C implementation allows asynchronous actions per device. e.g. A key can be pressed at the same time that
31
+ # the mouse is moving. Keep in mind that pauses must be added for other devices in order to line up the actions
32
+ # correctly when using asynchronous.
57
33
  #
58
- # Equivalent to:
59
- # driver.action.click(element).send_keys(key)
60
- # # or
61
- # driver.action.click.send_keys(key)
62
- #
63
- # @example Press a key
64
- #
65
- # driver.action.key_down(:control).perform
66
- #
67
- # @example Press a key on an element
68
- #
69
- # el = driver.find_element(id: "some_id")
70
- # driver.action.key_down(el, :shift).perform
34
+ # @param [Selenium::WebDriver::Remote::Bridge] bridge the bridge for the current driver instance
35
+ # @param [Selenium::WebDriver::Interactions::PointerInput] mouse PointerInput for the mouse.
36
+ # @param [Selenium::WebDriver::Interactions::KeyInput] keyboard KeyInput for the keyboard.
37
+ # @param [Boolean] async Whether to perform the actions asynchronously per device. Defaults to false for
38
+ # backwards compatibility.
39
+ # @return [ActionBuilder] A self reference.
71
40
  #
72
- # @overload key_down(key)
73
- # @param [:shift, :alt, :control, :command, :meta] key The modifier key to press
74
- # @overload key_down(element, key)
75
- # @param [Element] element An optional element to move to first
76
- # @param [:shift, :alt, :control, :command, :meta] key The modifier key to press
77
- # @raise [ArgumentError] if the given key is not a modifier
78
- # @return [ActionBuilder] A self reference
79
41
 
80
- def key_down(*args)
81
- @actions << [:mouse, :click, [args.shift]] if args.first.is_a? Element
82
-
83
- @actions << [:keyboard, :press, args]
84
- self
42
+ def initialize(bridge, mouse, keyboard, async = false)
43
+ # For backwards compatibility, automatically include mouse & keyboard
44
+ @bridge = bridge
45
+ @devices = [mouse, keyboard]
46
+ @async = async
85
47
  end
86
48
 
87
49
  #
88
- # Performs a modifier key release.
89
- # Releasing a non-depressed modifier key will yield undefined behaviour.
50
+ # Adds a PointerInput device of the given kind
90
51
  #
91
- # @example Release a key
52
+ # @example Add a touch pointer input device
92
53
  #
93
- # driver.action.key_up(:shift).perform
54
+ # builder = device.action
55
+ # builder.add_pointer_input('touch', :touch)
94
56
  #
95
- # @example Release a key from an element
57
+ # @param [String] name name for the device
58
+ # @param [Symbol] kind kind of pointer device to create
59
+ # @return [Interactions::PointerInput] The pointer input added
96
60
  #
97
- # el = driver.find_element(id: "some_id")
98
- # driver.action.key_up(el, :alt).perform
99
61
  #
100
- # @overload key_up(key)
101
- # @param [:shift, :alt, :control, :command, :meta] key The modifier key to release
102
- # @overload key_up(element, key)
103
- # @param [Element] element An optional element to move to first
104
- # @param [:shift, :alt, :control, :command, :meta] key The modifier key to release
105
- # @raise [ArgumentError] if the given key is not a modifier
106
- # @return [ActionBuilder] A self reference
107
- #
108
-
109
- def key_up(*args)
110
- @actions << [:mouse, :click, [args.shift]] if args.first.is_a? Element
111
62
 
112
- @actions << [:keyboard, :release, args]
113
- self
63
+ def add_pointer_input(kind, name)
64
+ new_input = Interactions.pointer(kind, name: name)
65
+ add_input(new_input)
66
+ new_input
114
67
  end
115
68
 
116
69
  #
117
- # Sends keys to the active element. This differs from calling
118
- # Element#send_keys(keys) on the active element in two ways:
119
- #
120
- # * The modifier keys included in this call are not released.
121
- # * There is no attempt to re-focus the element - so send_keys(:tab) for switching elements should work.
122
- #
123
- # @example Send the text "help" to an element
124
- #
125
- # el = driver.find_element(id: "some_id")
126
- # driver.action.send_keys(el, "help").perform
70
+ # Adds a KeyInput device
127
71
  #
128
- # @example Send the text "help" to the currently focused element
72
+ # @example Add a key input device
129
73
  #
130
- # driver.action.send_keys("help").perform
74
+ # builder = device.action
75
+ # builder.add_key_input('keyboard2')
131
76
  #
132
- # @overload send_keys(keys)
133
- # @param [Array, Symbol, String] keys The key(s) to press and release
134
- # @overload send_keys(element, keys)
135
- # @param [Element] element An optional element to move to first
136
- # @param [Array, Symbol, String] keys The key(s) to press and release
137
- # @return [ActionBuilder] A self reference
77
+ # @param [String] name name for the device
78
+ # @return [Interactions::KeyInput] The key input added
138
79
  #
139
80
 
140
- def send_keys(*args)
141
- @actions << [:mouse, :click, [args.shift]] if args.first.is_a? Element
142
-
143
- @actions << [:keyboard, :send_keys, args]
144
- self
81
+ def add_key_input(name)
82
+ new_input = Interactions.key(name)
83
+ add_input(new_input)
84
+ new_input
145
85
  end
146
86
 
147
87
  #
148
- # Clicks (without releasing) in the middle of the given element. This is
149
- # equivalent to:
150
- #
151
- # driver.action.move_to(element).click_and_hold
152
- #
153
- # @example Clicking and holding on some element
88
+ # Retrieves the input device for the given name
154
89
  #
155
- # el = driver.find_element(id: "some_id")
156
- # driver.action.click_and_hold(el).perform
157
- #
158
- # @param [Element] element the element to move to and click.
159
- # @return [ActionBuilder] A self reference.
90
+ # @param [String] name name of the input device
91
+ # @return [Selenium::WebDriver::Interactions::InputDevice] input device with given name
160
92
  #
161
93
 
162
- def click_and_hold(element = nil)
163
- @actions << [:mouse, :down, [element]]
164
- self
94
+ def get_device(name)
95
+ @devices.find { |device| device.name == name.to_s }
165
96
  end
166
97
 
167
98
  #
168
- # Releases the depressed left mouse button at the current mouse location.
99
+ # Retrieves the current PointerInput devices
169
100
  #
170
- # @example Releasing an element after clicking and holding it
171
- #
172
- # el = driver.find_element(id: "some_id")
173
- # driver.action.click_and_hold(el).release.perform
174
- #
175
- # @return [ActionBuilder] A self reference.
101
+ # @return [Array] array of current PointerInput devices
176
102
  #
177
103
 
178
- def release(element = nil)
179
- @actions << [:mouse, :up, [element]]
180
- self
104
+ def pointer_inputs
105
+ @devices.select { |device| device.type == Interactions::POINTER }
181
106
  end
182
107
 
183
108
  #
184
- # Clicks in the middle of the given element. Equivalent to:
185
- #
186
- # driver.action.move_to(element).click
187
- #
188
- # When no element is passed, the current mouse position will be clicked.
189
- #
190
- # @example Clicking on an element
109
+ # Retrieves the current KeyInput device
191
110
  #
192
- # el = driver.find_element(id: "some_id")
193
- # driver.action.click(el).perform
194
- #
195
- # @example Clicking at the current mouse position
196
- #
197
- # driver.action.click.perform
198
- #
199
- # @param [Selenium::WebDriver::Element] element An optional element to click.
200
- # @return [ActionBuilder] A self reference.
111
+ # @return [Selenium::WebDriver::Interactions::InputDevice] current KeyInput device
201
112
  #
202
113
 
203
- def click(element = nil)
204
- @actions << [:mouse, :click, [element]]
205
- self
114
+ def key_inputs
115
+ @devices.select { |device| device.type == Interactions::KEY }
206
116
  end
207
117
 
208
118
  #
209
- # Performs a double-click at middle of the given element. Equivalent to:
210
- #
211
- # driver.action.move_to(element).double_click
119
+ # Creates a pause for the given device of the given duration. If no duration is given, the pause will only wait
120
+ # for all actions to complete in that tick.
212
121
  #
213
- # @example Double click an element
122
+ # @example Send keys to an element
214
123
  #
215
- # el = driver.find_element(id: "some_id")
216
- # driver.action.double_click(el).perform
124
+ # action_builder = driver.action
125
+ # keyboard = action_builder.key_input
126
+ # el = driver.find_element(id: "some_id")
127
+ # driver.action.click(el).pause(keyboard).pause(keyboard).pause(keyboard).send_keys('keys').perform
217
128
  #
218
- # @param [Selenium::WebDriver::Element] element An optional element to move to.
129
+ # @param [InputDevice] device Input device to pause
130
+ # @param [Float] duration Duration to pause
219
131
  # @return [ActionBuilder] A self reference.
220
132
  #
221
133
 
222
- def double_click(element = nil)
223
- @actions << [:mouse, :double_click, [element]]
134
+ def pause(device, duration = nil)
135
+ device.create_pause(duration)
224
136
  self
225
137
  end
226
138
 
227
139
  #
228
- # Moves the mouse to the middle of the given element. The element is scrolled into
229
- # view and its location is calculated using getBoundingClientRect. Then the
230
- # mouse is moved to optional offset coordinates from the element.
231
- #
232
- # Note that when using offsets, both coordinates need to be passed.
233
- #
234
- # @example Scroll element into view and move the mouse to it
140
+ # Creates multiple pauses for the given device of the given duration.
235
141
  #
236
- # el = driver.find_element(id: "some_id")
237
- # driver.action.move_to(el).perform
238
- #
239
- # @example
142
+ # @example Send keys to an element
240
143
  #
144
+ # action_builder = driver.action
145
+ # keyboard = action_builder.key_input
241
146
  # el = driver.find_element(id: "some_id")
242
- # driver.action.move_to(el, 100, 100).perform
147
+ # driver.action.click(el).pauses(keyboard, 3).send_keys('keys').perform
243
148
  #
244
- # @param [Selenium::WebDriver::Element] element to move to.
245
- # @param [Integer] right_by Optional offset from the top-left corner. A negative value means
246
- # coordinates right from the element.
247
- # @param [Integer] down_by Optional offset from the top-left corner. A negative value means
248
- # coordinates above the element.
149
+ # @param [InputDevice] device Input device to pause
150
+ # @param [Integer] number of pauses to add for the device
151
+ # @param [Float] duration Duration to pause
249
152
  # @return [ActionBuilder] A self reference.
250
153
  #
251
154
 
252
- def move_to(element, right_by = nil, down_by = nil)
253
- @actions << if right_by && down_by
254
- [:mouse, :move_to, [element, Integer(right_by), Integer(down_by)]]
255
- else
256
- [:mouse, :move_to, [element]]
257
- end
258
-
155
+ def pauses(device, number, duration = nil)
156
+ number.times { device.create_pause(duration) }
259
157
  self
260
158
  end
261
159
 
262
160
  #
263
- # Moves the mouse from its current position (or 0,0) by the given offset.
264
- # If the coordinates provided are outside the viewport (the mouse will
265
- # end up outside the browser window) then the viewport is scrolled to
266
- # match.
267
- #
268
- # @example Move the mouse to a certain offset from its current position
269
- #
270
- # driver.action.move_by(100, 100).perform
271
- #
272
- # @param [Integer] right_by horizontal offset. A negative value means moving the
273
- # mouse left.
274
- # @param [Integer] down_by vertical offset. A negative value means moving the mouse
275
- # up.
276
- # @return [ActionBuilder] A self reference.
277
- # @raise [MoveTargetOutOfBoundsError] if the provided offset is outside
278
- # the document's boundaries.
161
+ # Executes the actions added to the builder.
279
162
  #
280
163
 
281
- def move_by(right_by, down_by)
282
- @actions << [:mouse, :move_by, [Integer(right_by), Integer(down_by)]]
283
- self
164
+ def perform
165
+ @bridge.send_actions @devices.map(&:encode).compact
166
+ clear_all_actions
167
+ nil
284
168
  end
285
169
 
286
170
  #
287
- # Performs a context-click at middle of the given element. First performs
288
- # a move_to to the location of the element.
289
- #
290
- # @example Context-click at middle of given element
291
- #
292
- # el = driver.find_element(id: "some_id")
293
- # driver.action.context_click(el).perform
294
- #
295
- # @param [Selenium::WebDriver::Element] element An element to context click.
296
- # @return [ActionBuilder] A self reference.
171
+ # Clears all actions from the builder.
297
172
  #
298
173
 
299
- def context_click(element = nil)
300
- @actions << [:mouse, :context_click, [element]]
301
- self
174
+ def clear_all_actions
175
+ @devices.each(&:clear_actions)
302
176
  end
303
177
 
304
178
  #
305
- # A convenience method that performs click-and-hold at the location of the
306
- # source element, moves to the location of the target element, then
307
- # releases the mouse.
308
- #
309
- # @example Drag and drop one element onto another
310
- #
311
- # el1 = driver.find_element(id: "some_id1")
312
- # el2 = driver.find_element(id: "some_id2")
313
- # driver.action.drag_and_drop(el1, el2).perform
314
- #
315
- # @param [Selenium::WebDriver::Element] source element to emulate button down at.
316
- # @param [Selenium::WebDriver::Element] target element to move to and release the
317
- # mouse at.
318
- # @return [ActionBuilder] A self reference.
179
+ # Releases all action states from the browser.
319
180
  #
320
181
 
321
- def drag_and_drop(source, target)
322
- click_and_hold source
323
- move_to target
324
- release
325
-
326
- self
182
+ def release_actions
183
+ @bridge.release_actions
327
184
  end
328
185
 
186
+ private
187
+
329
188
  #
330
- # A convenience method that performs click-and-hold at the location of
331
- # the source element, moves by a given offset, then releases the mouse.
332
- #
333
- # @example Drag and drop an element by offset
189
+ # Adds pauses for all devices but the given devices
334
190
  #
335
- # el = driver.find_element(id: "some_id1")
336
- # driver.action.drag_and_drop_by(el, 100, 100).perform
337
- #
338
- # @param [Selenium::WebDriver::Element] source Element to emulate button down at.
339
- # @param [Integer] right_by horizontal move offset.
340
- # @param [Integer] down_by vertical move offset.
341
- # @return [ActionBuilder] A self reference.
191
+ # @param [Array[InputDevice]] action_devices Array of Input Devices performing an action in this tick.
342
192
  #
343
193
 
344
- def drag_and_drop_by(source, right_by, down_by)
345
- click_and_hold source
346
- move_by right_by, down_by
347
- release
194
+ def tick(*action_devices)
195
+ return if @async
348
196
 
349
- self
197
+ @devices.each { |device| device.create_pause unless action_devices.include? device }
350
198
  end
351
199
 
352
200
  #
353
- # Executes the actions added to the builder.
201
+ # Adds an InputDevice
354
202
  #
355
203
 
356
- def perform
357
- @actions.each do |receiver, method, args|
358
- @devices.fetch(receiver).__send__(method, *args)
204
+ def add_input(device)
205
+ unless @async
206
+ max_device = @devices.max { |a, b| a.actions.length <=> b.actions.length }
207
+ pauses(device, max_device.actions.length)
359
208
  end
360
-
361
- nil
209
+ @devices << device
362
210
  end
363
211
  end # ActionBuilder
364
212
  end # WebDriver