selenium-webdriver 3.0.0.beta4.0 → 4.0.0.alpha5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +679 -0
  3. data/Gemfile +2 -0
  4. data/LICENSE +1 -1
  5. data/README.md +1 -1
  6. data/lib/selenium-webdriver.rb +2 -2
  7. data/lib/selenium/server.rb +23 -16
  8. data/lib/selenium/webdriver.rb +43 -25
  9. data/lib/selenium/webdriver/atoms.rb +20 -1
  10. data/lib/selenium/webdriver/atoms/findElements.js +122 -0
  11. data/lib/selenium/webdriver/atoms/getAttribute.js +84 -11
  12. data/lib/selenium/webdriver/atoms/isDisplayed.js +100 -0
  13. data/lib/selenium/webdriver/chrome.rb +15 -20
  14. data/lib/selenium/webdriver/chrome/bridge.rb +29 -66
  15. data/lib/selenium/webdriver/{edge/service.rb → chrome/driver.rb} +19 -22
  16. data/lib/selenium/webdriver/chrome/options.rb +221 -0
  17. data/lib/selenium/webdriver/chrome/profile.rb +6 -7
  18. data/lib/selenium/webdriver/chrome/service.rb +20 -20
  19. data/lib/selenium/webdriver/common.rb +19 -11
  20. data/lib/selenium/webdriver/common/action_builder.rb +98 -246
  21. data/lib/selenium/webdriver/common/alert.rb +2 -7
  22. data/lib/selenium/webdriver/common/driver.rb +89 -51
  23. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +45 -0
  24. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +50 -0
  25. data/lib/selenium/webdriver/common/driver_extensions/{has_input_devices.rb → has_debugger.rb} +12 -25
  26. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +38 -0
  27. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +3 -5
  28. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +51 -0
  29. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +3 -3
  30. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +51 -0
  31. data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +2 -2
  32. data/lib/selenium/webdriver/common/driver_extensions/has_session_id.rb +2 -2
  33. data/lib/selenium/webdriver/common/driver_extensions/has_web_storage.rb +2 -2
  34. data/lib/selenium/webdriver/common/driver_extensions/rotatable.rb +4 -4
  35. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +9 -3
  36. data/lib/selenium/webdriver/common/driver_extensions/uploads_files.rb +3 -5
  37. data/lib/selenium/webdriver/common/element.rb +32 -10
  38. data/lib/selenium/webdriver/common/error.rb +103 -121
  39. data/lib/selenium/webdriver/common/file_reaper.rb +3 -5
  40. data/lib/selenium/webdriver/common/html5/local_storage.rb +2 -2
  41. data/lib/selenium/webdriver/common/html5/session_storage.rb +2 -2
  42. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +3 -2
  43. data/lib/selenium/webdriver/common/interactions/input_device.rb +54 -0
  44. data/lib/selenium/webdriver/common/interactions/interaction.rb +53 -0
  45. data/lib/selenium/webdriver/{phantomjs.rb → common/interactions/interactions.rb} +17 -20
  46. data/lib/selenium/webdriver/common/interactions/key_actions.rb +145 -0
  47. data/lib/selenium/webdriver/common/interactions/key_input.rb +66 -0
  48. data/lib/selenium/webdriver/common/interactions/none_input.rb +36 -0
  49. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +363 -0
  50. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +139 -0
  51. data/lib/selenium/webdriver/common/keys.rb +37 -15
  52. data/lib/selenium/webdriver/common/log_entry.rb +4 -4
  53. data/lib/selenium/webdriver/common/logger.rb +147 -0
  54. data/lib/selenium/webdriver/common/logs.rb +2 -2
  55. data/lib/selenium/webdriver/common/manager.rb +177 -0
  56. data/lib/selenium/webdriver/common/navigation.rb +2 -2
  57. data/lib/selenium/webdriver/common/options.rb +47 -105
  58. data/lib/selenium/webdriver/common/platform.rb +44 -38
  59. data/lib/selenium/webdriver/common/port_prober.rb +8 -19
  60. data/lib/selenium/webdriver/common/profile_helper.rb +13 -5
  61. data/lib/selenium/webdriver/common/proxy.rb +14 -8
  62. data/lib/selenium/webdriver/common/search_context.rb +16 -20
  63. data/lib/selenium/webdriver/common/service.rb +115 -30
  64. data/lib/selenium/webdriver/common/socket_lock.rb +12 -7
  65. data/lib/selenium/webdriver/common/socket_poller.rb +29 -22
  66. data/lib/selenium/webdriver/common/target_locator.rb +6 -6
  67. data/lib/selenium/webdriver/common/timeouts.rb +2 -2
  68. data/lib/selenium/webdriver/common/wait.rb +14 -8
  69. data/lib/selenium/webdriver/common/window.rb +38 -2
  70. data/lib/selenium/webdriver/common/zipper.rb +3 -5
  71. data/lib/selenium/webdriver/edge.rb +33 -20
  72. data/lib/selenium/webdriver/edge_chrome/bridge.rb +30 -0
  73. data/lib/selenium/webdriver/edge_chrome/driver.rb +38 -0
  74. data/lib/selenium/webdriver/{common/driver_extensions/has_touch_screen.rb → edge_chrome/options.rb} +10 -12
  75. data/lib/selenium/webdriver/edge_chrome/profile.rb +33 -0
  76. data/lib/selenium/webdriver/edge_chrome/service.rb +36 -0
  77. data/lib/selenium/webdriver/edge_html/driver.rb +39 -0
  78. data/lib/selenium/webdriver/edge_html/options.rb +91 -0
  79. data/lib/selenium/webdriver/edge_html/service.rb +47 -0
  80. data/lib/selenium/webdriver/firefox.rb +24 -29
  81. data/lib/selenium/webdriver/firefox/bridge.rb +15 -39
  82. data/lib/selenium/webdriver/firefox/{util.rb → driver.rb} +13 -19
  83. data/lib/selenium/webdriver/firefox/extension.rb +28 -8
  84. data/lib/selenium/webdriver/firefox/options.rb +162 -0
  85. data/lib/selenium/webdriver/firefox/profile.rb +23 -82
  86. data/lib/selenium/webdriver/firefox/profiles_ini.rb +5 -5
  87. data/lib/selenium/webdriver/firefox/service.rb +18 -37
  88. data/lib/selenium/webdriver/ie.rb +13 -19
  89. data/lib/selenium/webdriver/ie/driver.rb +40 -0
  90. data/lib/selenium/webdriver/ie/options.rb +118 -0
  91. data/lib/selenium/webdriver/ie/service.rb +20 -20
  92. data/lib/selenium/webdriver/remote.rb +15 -17
  93. data/lib/selenium/webdriver/remote/bridge.rb +281 -300
  94. data/lib/selenium/webdriver/remote/capabilities.rb +102 -83
  95. data/lib/selenium/webdriver/remote/commands.rb +132 -192
  96. data/lib/selenium/webdriver/remote/driver.rb +52 -0
  97. data/lib/selenium/webdriver/remote/http/common.rb +23 -13
  98. data/lib/selenium/webdriver/remote/http/curb.rb +10 -7
  99. data/lib/selenium/webdriver/remote/http/default.rb +52 -32
  100. data/lib/selenium/webdriver/remote/http/persistent.rb +8 -4
  101. data/lib/selenium/webdriver/remote/response.rb +32 -35
  102. data/lib/selenium/webdriver/remote/server_error.rb +2 -2
  103. data/lib/selenium/webdriver/safari.rb +24 -22
  104. data/lib/selenium/webdriver/safari/bridge.rb +21 -21
  105. data/lib/selenium/webdriver/safari/driver.rb +41 -0
  106. data/lib/selenium/webdriver/safari/options.rb +66 -0
  107. data/lib/selenium/webdriver/safari/service.rb +8 -24
  108. data/lib/selenium/webdriver/support.rb +3 -2
  109. data/lib/selenium/webdriver/support/abstract_event_listener.rb +2 -2
  110. data/lib/selenium/webdriver/support/block_event_listener.rb +3 -3
  111. data/lib/selenium/webdriver/support/color.rb +13 -13
  112. data/lib/selenium/webdriver/support/escaper.rb +2 -2
  113. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  114. data/lib/selenium/webdriver/support/relative_locator.rb +51 -0
  115. data/lib/selenium/webdriver/support/select.rb +20 -21
  116. data/lib/selenium/webdriver/version.rb +24 -0
  117. data/selenium-webdriver.gemspec +31 -17
  118. metadata +331 -248
  119. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  120. data/lib/selenium/webdriver/common/keyboard.rb +0 -69
  121. data/lib/selenium/webdriver/common/mouse.rb +0 -88
  122. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -81
  123. data/lib/selenium/webdriver/common/touch_screen.rb +0 -121
  124. data/lib/selenium/webdriver/common/w3c_error.rb +0 -191
  125. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  126. data/lib/selenium/webdriver/edge/legacy_support.rb +0 -117
  127. data/lib/selenium/webdriver/firefox/binary.rb +0 -186
  128. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  129. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  130. data/lib/selenium/webdriver/firefox/launcher.rb +0 -114
  131. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  132. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  133. data/lib/selenium/webdriver/firefox/w3c_bridge.rb +0 -79
  134. data/lib/selenium/webdriver/ie/bridge.rb +0 -76
  135. data/lib/selenium/webdriver/phantomjs/bridge.rb +0 -65
  136. data/lib/selenium/webdriver/phantomjs/service.rb +0 -66
  137. data/lib/selenium/webdriver/remote/w3c_bridge.rb +0 -682
  138. data/lib/selenium/webdriver/remote/w3c_capabilities.rb +0 -228
  139. data/lib/selenium/webdriver/remote/w3c_commands.rb +0 -135
@@ -1,114 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # Licensed to the Software Freedom Conservancy (SFC) under one
4
- # or more contributor license agreements. See the NOTICE file
5
- # distributed with this work for additional information
6
- # regarding copyright ownership. The SFC licenses this file
7
- # to you under the Apache License, Version 2.0 (the
8
- # "License"); you may not use this file except in compliance
9
- # with the License. You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing,
14
- # software distributed under the License is distributed on an
15
- # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
- # KIND, either express or implied. See the License for the
17
- # specific language governing permissions and limitations
18
- # under the License.
19
-
20
- module Selenium
21
- module WebDriver
22
- module Firefox
23
- # @api private
24
- class Launcher
25
- SOCKET_LOCK_TIMEOUT = 45
26
- STABLE_CONNECTION_TIMEOUT = 60
27
-
28
- def initialize(binary, port, profile = nil)
29
- @binary = binary
30
- @port = Integer(port)
31
-
32
- raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
33
-
34
- if profile.is_a? Profile
35
- @profile = profile
36
- else
37
- @profile_name = profile
38
- @profile = nil
39
- end
40
-
41
- @host = '127.0.0.1'
42
- end
43
-
44
- def url
45
- "http://#{@host}:#{@port}/hub"
46
- end
47
-
48
- def launch
49
- socket_lock.locked do
50
- find_free_port
51
- create_profile
52
- start
53
- connect_until_stable
54
- end
55
-
56
- self
57
- end
58
-
59
- def quit
60
- @binary.quit
61
- FileReaper.reap(@profile_dir) if @profile_dir
62
- end
63
-
64
- def find_free_port
65
- @port = PortProber.above @port
66
- end
67
-
68
- def create_profile
69
- fetch_profile unless @profile
70
-
71
- @profile.add_webdriver_extension
72
- @profile.port = @port
73
-
74
- @profile_dir = @profile.layout_on_disk
75
- FileReaper << @profile_dir
76
- end
77
-
78
- def start
79
- assert_profile
80
- @binary.start_with @profile, @profile_dir, '-foreground'
81
- end
82
-
83
- def connect_until_stable
84
- poller = SocketPoller.new(@host, @port, STABLE_CONNECTION_TIMEOUT)
85
-
86
- return if poller.connected?
87
- @binary.quit
88
- error = "unable to obtain stable firefox connection in #{STABLE_CONNECTION_TIMEOUT} seconds (#{@host}:#{@port})"
89
- raise Error::WebDriverError, error
90
- end
91
-
92
- def fetch_profile
93
- if @profile_name
94
- @profile = Profile.from_name @profile_name
95
-
96
- unless @profile
97
- raise Error::WebDriverError, "unable to find profile named: #{@profile_name.inspect}"
98
- end
99
- else
100
- @profile = Profile.new
101
- end
102
- end
103
-
104
- def assert_profile
105
- raise Error::WebDriverError, 'must create_profile first' unless @profile && @profile_dir
106
- end
107
-
108
- def socket_lock
109
- @socket_lock ||= SocketLock.new(@port - 1, SOCKET_LOCK_TIMEOUT)
110
- end
111
- end # Launcher
112
- end # Firefox
113
- end # WebDriver
114
- end # Selenium
@@ -1,79 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # Licensed to the Software Freedom Conservancy (SFC) under one
4
- # or more contributor license agreements. See the NOTICE file
5
- # distributed with this work for additional information
6
- # regarding copyright ownership. The SFC licenses this file
7
- # to you under the Apache License, Version 2.0 (the
8
- # "License"); you may not use this file except in compliance
9
- # with the License. You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing,
14
- # software distributed under the License is distributed on an
15
- # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
- # KIND, either express or implied. See the License for the
17
- # specific language governing permissions and limitations
18
- # under the License.
19
-
20
- module Selenium
21
- module WebDriver
22
- module Firefox
23
- # @api private
24
- class W3CBridge < Remote::W3CBridge
25
- def initialize(opts = {})
26
- port = opts.delete(:port) || Service::DEFAULT_PORT
27
- opts[:desired_capabilities] = create_capabilities(opts)
28
- service_args = opts.delete(:service_args) || {}
29
-
30
- @service = Service.new(Firefox.driver_path, port, *extract_service_args(service_args))
31
- @service.start
32
- opts[:url] = @service.uri
33
-
34
- super(opts)
35
- end
36
-
37
- def browser
38
- :firefox
39
- end
40
-
41
- def driver_extensions
42
- [
43
- DriverExtensions::TakesScreenshot,
44
- DriverExtensions::HasInputDevices,
45
- DriverExtensions::HasWebStorage
46
- ]
47
- end
48
-
49
- def quit
50
- super
51
- ensure
52
- @service.stop if @service
53
- end
54
-
55
- private
56
-
57
- def create_capabilities(opts)
58
- caps = opts.delete(:desired_capabilities) || Remote::W3CCapabilities.firefox
59
- firefox_options_caps = caps[:firefox_options] || {}
60
- caps[:firefox_options] = firefox_options_caps.merge(opts[:firefox_options] || {})
61
-
62
- Binary.path = caps[:firefox_options][:binary] if caps[:firefox_options].key?(:binary)
63
- caps
64
- end
65
-
66
- def extract_service_args(args = {})
67
- service_args = []
68
- service_args << "--binary=#{args[:binary]}" if args.key?(:binary)
69
- service_args << "–-log=#{args[:log]}" if args.key?(:log)
70
- service_args << "–-marionette-port=#{args[:marionette_port]}" if args.key?(:marionette_port)
71
- service_args << "–-host=#{args[:host]}" if args.key?(:host)
72
- service_args << "–-port=#{args[:port]}" if args.key?(:port)
73
- service_args
74
- end
75
-
76
- end # W3CBridge
77
- end # Firefox
78
- end # WebDriver
79
- end # Selenium
@@ -1,76 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # Licensed to the Software Freedom Conservancy (SFC) under one
4
- # or more contributor license agreements. See the NOTICE file
5
- # distributed with this work for additional information
6
- # regarding copyright ownership. The SFC licenses this file
7
- # to you under the Apache License, Version 2.0 (the
8
- # "License"); you may not use this file except in compliance
9
- # with the License. You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing,
14
- # software distributed under the License is distributed on an
15
- # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
- # KIND, either express or implied. See the License for the
17
- # specific language governing permissions and limitations
18
- # under the License.
19
-
20
- module Selenium
21
- module WebDriver
22
- module IE
23
- #
24
- # @api private
25
- #
26
-
27
- class Bridge < Remote::Bridge
28
- def initialize(opts = {})
29
- port = opts.delete(:port) || Service::DEFAULT_PORT
30
- service_args = opts.delete(:service_args) || {}
31
- service_args = match_legacy(opts, service_args)
32
- @service = Service.new(IE.driver_path, port, *extract_service_args(service_args))
33
- @service.start
34
- opts[:url] = @service.uri
35
-
36
- caps = opts[:desired_capabilities] ||= Remote::Capabilities.internet_explorer
37
- caps[:ignore_protected_mode_settings] = true if opts.delete(:introduce_flakiness_by_ignoring_security_domains)
38
- caps[:native_events] = opts.delete(:native_events) != false
39
-
40
- super(opts)
41
- end
42
-
43
- def browser
44
- :internet_explorer
45
- end
46
-
47
- def driver_extensions
48
- [DriverExtensions::TakesScreenshot, DriverExtensions::HasInputDevices]
49
- end
50
-
51
- def quit
52
- super
53
- ensure
54
- @service.stop if @service
55
- end
56
-
57
- private
58
-
59
- def match_legacy(opts, args)
60
- args[:log_level] = opts.delete(:log_level) if opts.key?(:log_level)
61
- args[:log_file] = opts.delete(:log_file) if opts.key?(:log_file)
62
- args[:implementation] = opts.delete(:implementation) if opts.key?(:implementation)
63
- args
64
- end
65
-
66
- def extract_service_args(args)
67
- service_args = []
68
- service_args << "--log-level=#{args.delete(:log_level).to_s.upcase}" if args.key?(:log_level)
69
- service_args << "--log-file=#{args.delete(:log_file)}" if args.key?(:log_file)
70
- service_args << "--implementation=#{args.delete(:implementation).to_s.upcase}" if args.key?(:implementation)
71
- service_args
72
- end
73
- end # Bridge
74
- end # IE
75
- end # WebDriver
76
- end # Selenium
@@ -1,65 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # Licensed to the Software Freedom Conservancy (SFC) under one
4
- # or more contributor license agreements. See the NOTICE file
5
- # distributed with this work for additional information
6
- # regarding copyright ownership. The SFC licenses this file
7
- # to you under the Apache License, Version 2.0 (the
8
- # "License"); you may not use this file except in compliance
9
- # with the License. You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing,
14
- # software distributed under the License is distributed on an
15
- # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
- # KIND, either express or implied. See the License for the
17
- # specific language governing permissions and limitations
18
- # under the License.
19
-
20
- module Selenium
21
- module WebDriver
22
- module PhantomJS
23
- #
24
- # @api private
25
- #
26
-
27
- class Bridge < Remote::Bridge
28
- def initialize(opts = {})
29
- port = opts.delete(:port) || Service::DEFAULT_PORT
30
- opts[:desired_capabilities] ||= Remote::Capabilities.phantomjs
31
-
32
- unless opts.key?(:url)
33
- args = opts.delete(:args) || opts[:desired_capabilities]['phantomjs.cli.args']
34
- @service = Service.new(PhantomJS.path, port, *args)
35
- @service.start
36
- opts[:url] = @service.uri
37
- end
38
-
39
- super(opts)
40
- end
41
-
42
- def browser
43
- :phantomjs
44
- end
45
-
46
- def driver_extensions
47
- [
48
- DriverExtensions::TakesScreenshot,
49
- DriverExtensions::HasInputDevices
50
- ]
51
- end
52
-
53
- def capabilities
54
- @capabilities ||= Remote::Capabilities.phantomjs
55
- end
56
-
57
- def quit
58
- super
59
- ensure
60
- @service.stop if @service
61
- end
62
- end # Bridge
63
- end # PhantomJS
64
- end # WebDriver
65
- end # Selenium
@@ -1,66 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # Licensed to the Software Freedom Conservancy (SFC) under one
4
- # or more contributor license agreements. See the NOTICE file
5
- # distributed with this work for additional information
6
- # regarding copyright ownership. The SFC licenses this file
7
- # to you under the Apache License, Version 2.0 (the
8
- # "License"); you may not use this file except in compliance
9
- # with the License. You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing,
14
- # software distributed under the License is distributed on an
15
- # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
- # KIND, either express or implied. See the License for the
17
- # specific language governing permissions and limitations
18
- # under the License.
19
-
20
- module Selenium
21
- module WebDriver
22
- module PhantomJS
23
- #
24
- # @api private
25
- #
26
-
27
- class Service < WebDriver::Service
28
- DEFAULT_PORT = 8910
29
-
30
- private
31
-
32
- def start_process
33
- server_command = [@executable_path, "--webdriver=#{@port}", *@extra_args]
34
- @process = ChildProcess.build(*server_command.compact)
35
-
36
- if $DEBUG
37
- @process.io.inherit!
38
- elsif Platform.jruby?
39
- # apparently we need to read the output for phantomjs to work on jruby
40
- @process.io.stdout = @process.io.stderr = File.new(Platform.null_device, 'w')
41
- end
42
-
43
- @process.start
44
- end
45
-
46
- def stop_process
47
- super
48
- return unless Platform.jruby? && !$DEBUG
49
- begin
50
- @process.io.close
51
- rescue
52
- nil
53
- end
54
- end
55
-
56
- def stop_server
57
- connect_to_server { |http| http.get('/shutdown') }
58
- end
59
-
60
- def cannot_connect_error_text
61
- "unable to connect to phantomjs @ #{uri} after #{START_TIMEOUT} seconds"
62
- end
63
- end # Service
64
- end # PhantomJS
65
- end # WebDriver
66
- end # Selenium
@@ -1,682 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # Licensed to the Software Freedom Conservancy (SFC) under one
4
- # or more contributor license agreements. See the NOTICE file
5
- # distributed with this work for additional information
6
- # regarding copyright ownership. The SFC licenses this file
7
- # to you under the Apache License, Version 2.0 (the
8
- # "License"); you may not use this file except in compliance
9
- # with the License. You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing,
14
- # software distributed under the License is distributed on an
15
- # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
- # KIND, either express or implied. See the License for the
17
- # specific language governing permissions and limitations
18
- # under the License.
19
-
20
- require 'json'
21
-
22
- module Selenium
23
- module WebDriver
24
- module Remote
25
- #
26
- # Low level bridge to the remote server, through which the rest of the API works.
27
- #
28
- # @api private
29
- #
30
-
31
- class W3CBridge
32
- include BridgeHelper
33
- include Atoms
34
-
35
- # TODO: constant shouldn't be modified in class
36
- COMMANDS = {}
37
-
38
- #
39
- # Defines a wrapper method for a command, which ultimately calls #execute.
40
- #
41
- # @param name [Symbol]
42
- # name of the resulting method
43
- # @param verb [Symbol]
44
- # the appropriate http verb, such as :get, :post, or :delete
45
- # @param url [String]
46
- # a URL template, which can include some arguments, much like the definitions on the server.
47
- # the :session_id parameter is implicitly handled, but the remainder will become required method arguments.
48
- #
49
-
50
- def self.command(name, verb, url)
51
- COMMANDS[name] = [verb, url.freeze]
52
- end
53
-
54
- attr_accessor :context, :http, :file_detector
55
- attr_reader :capabilities
56
-
57
- #
58
- # Initializes the bridge with the given server URL.
59
- #
60
- # @param url [String] url for the remote server
61
- # @param http_client [Object] an HTTP client instance that implements the same protocol as Http::Default
62
- # @param desired_capabilities [Capabilities] an instance of Remote::Capabilities describing the capabilities you want
63
- #
64
-
65
- def initialize(opts = {})
66
- edge_check(opts)
67
-
68
- opts = opts.dup
69
-
70
- port = opts.delete(:port) || 4444
71
- http_client = opts.delete(:http_client) { Http::Default.new }
72
- desired_capabilities = opts.delete(:desired_capabilities) { W3CCapabilities.firefox }
73
- url = opts.delete(:url) { "http://#{Platform.localhost}:#{port}/wd/hub" }
74
-
75
- desired_capabilities = W3CCapabilities.send(desired_capabilities) if desired_capabilities.is_a? Symbol
76
-
77
- desired_capabilities[:marionette] = opts.delete(:marionette) unless opts[:marionette].nil?
78
-
79
- unless opts.empty?
80
- raise ArgumentError, "unknown option#{'s' if opts.size != 1}: #{opts.inspect}"
81
- end
82
-
83
- uri = url.is_a?(URI) ? url : URI.parse(url)
84
- uri.path += '/' unless uri.path =~ %r{\/$}
85
-
86
- http_client.server_url = uri
87
-
88
- @http = http_client
89
- @capabilities = create_session(desired_capabilities)
90
- @file_detector = nil
91
- end
92
-
93
- def browser
94
- @browser ||= (
95
- name = @capabilities.browser_name
96
- name ? name.tr(' ', '_').to_sym : 'unknown'
97
- )
98
- end
99
-
100
- def edge_check(opts)
101
- caps = opts[:desired_capabilities]
102
- return unless caps && caps[:browser_name] && caps[:browser_name] == 'MicrosoftEdge'
103
- require_relative '../edge/legacy_support'
104
- extend Edge::LegacySupport
105
- end
106
-
107
- def driver_extensions
108
- [
109
- DriverExtensions::HasInputDevices,
110
- DriverExtensions::UploadsFiles,
111
- DriverExtensions::TakesScreenshot,
112
- DriverExtensions::HasSessionId,
113
- DriverExtensions::Rotatable,
114
- DriverExtensions::HasTouchScreen,
115
- DriverExtensions::HasRemoteStatus,
116
- DriverExtensions::HasWebStorage
117
- ]
118
- end
119
-
120
- #
121
- # Returns the current session ID.
122
- #
123
-
124
- def session_id
125
- @session_id || raise(Error::WebDriverError, 'no current session exists')
126
- end
127
-
128
- def create_session(desired_capabilities)
129
- # TODO - Remove this when Mozilla fixes bug
130
- desired_capabilities[:browser_name] = 'firefox' if desired_capabilities[:browser_name] == 'Firefox'
131
-
132
- resp = raw_execute :newSession, {}, {desiredCapabilities: desired_capabilities}
133
- @session_id = resp['sessionId']
134
- return W3CCapabilities.json_create resp['value'] if @session_id
135
-
136
- raise Error::WebDriverError, 'no sessionId in returned payload'
137
- end
138
-
139
- def status
140
- jwp = Selenium::WebDriver::Remote::Bridge::COMMANDS[:status]
141
- self.class.command(:status, jwp.first, jwp.last)
142
- execute :status
143
- end
144
-
145
- def get(url)
146
- execute :get, {}, {url: url}
147
- end
148
-
149
- def implicit_wait_timeout=(milliseconds)
150
- timeout('implicit', milliseconds)
151
- end
152
-
153
- def script_timeout=(milliseconds)
154
- timeout('script', milliseconds)
155
- end
156
-
157
- def timeout(type, milliseconds)
158
- execute :setTimeout, {}, {type: type, ms: milliseconds}
159
- end
160
-
161
- #
162
- # alerts
163
- #
164
-
165
- def accept_alert
166
- execute :acceptAlert
167
- end
168
-
169
- def dismiss_alert
170
- execute :dismissAlert
171
- end
172
-
173
- def alert=(keys)
174
- execute :sendAlertText, {}, {handler: 'prompt', text: keys}
175
- end
176
-
177
- def alert_text
178
- execute :getAlertText
179
- end
180
-
181
- #
182
- # navigation
183
- #
184
-
185
- def go_back
186
- execute :back
187
- end
188
-
189
- def go_forward
190
- execute :forward
191
- end
192
-
193
- def url
194
- execute :getCurrentUrl
195
- end
196
-
197
- def title
198
- execute :getTitle
199
- end
200
-
201
- def page_source
202
- execute_script('var source = document.documentElement.outerHTML;' \
203
- 'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
204
- 'return source;')
205
- end
206
-
207
- def switch_to_window(name)
208
- execute :switchToWindow, {}, {handle: name}
209
- end
210
-
211
- def switch_to_frame(id)
212
- id = find_element_by('id', id) if id.is_a? String
213
- execute :switchToFrame, {}, {id: id}
214
- end
215
-
216
- def switch_to_parent_frame
217
- execute :switchToParentFrame
218
- end
219
-
220
- def switch_to_default_content
221
- switch_to_frame nil
222
- end
223
-
224
- QUIT_ERRORS = [IOError].freeze
225
-
226
- def quit
227
- execute :deleteSession
228
- http.close
229
- rescue *QUIT_ERRORS
230
- end
231
-
232
- def close
233
- execute :closeWindow
234
- end
235
-
236
- def refresh
237
- execute :refresh
238
- end
239
-
240
- #
241
- # window handling
242
- #
243
-
244
- def window_handles
245
- execute :getWindowHandles
246
- end
247
-
248
- def window_handle
249
- execute :getWindowHandle
250
- end
251
-
252
- def resize_window(width, height, handle = :current)
253
- unless handle == :current
254
- raise Error::WebDriverError, 'Switch to desired window before changing its size'
255
- end
256
- execute :setWindowSize, {}, {width: width,
257
- height: height}
258
- end
259
-
260
- def maximize_window(handle = :current)
261
- unless handle == :current
262
- raise Error::UnsupportedOperationError, 'Switch to desired window before changing its size'
263
- end
264
- execute :maximizeWindow
265
- end
266
-
267
- def full_screen_window
268
- execute :fullscreenWindow
269
- end
270
-
271
- def window_size(handle = :current)
272
- unless handle == :current
273
- raise Error::UnsupportedOperationError, 'Switch to desired window before getting its size'
274
- end
275
- data = execute :getWindowSize
276
-
277
- Dimension.new data['width'], data['height']
278
- end
279
-
280
- def reposition_window(_x, _y, _handle = nil)
281
- raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting the Window Position'
282
- end
283
-
284
- def window_position(_handle = nil)
285
- raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting the Window Position'
286
- end
287
-
288
- def screenshot
289
- execute :takeScreenshot
290
- end
291
-
292
- #
293
- # HTML 5
294
- #
295
-
296
- def local_storage_item(key, value = nil)
297
- if value
298
- execute_script("localStorage.setItem('#{key}', '#{value}')")
299
- else
300
- execute_script("return localStorage.getItem('#{key}')")
301
- end
302
- end
303
-
304
- def remove_local_storage_item(key)
305
- execute_script("localStorage.removeItem('#{key}')")
306
- end
307
-
308
- def local_storage_keys
309
- execute_script('return Object.keys(localStorage)')
310
- end
311
-
312
- def clear_local_storage
313
- execute_script('localStorage.clear()')
314
- end
315
-
316
- def local_storage_size
317
- execute_script('return localStorage.length')
318
- end
319
-
320
- def session_storage_item(key, value = nil)
321
- if value
322
- execute_script("sessionStorage.setItem('#{key}', '#{value}')")
323
- else
324
- execute_script("return sessionStorage.getItem('#{key}')")
325
- end
326
- end
327
-
328
- def remove_session_storage_item(key)
329
- execute_script("sessionStorage.removeItem('#{key}')")
330
- end
331
-
332
- def session_storage_keys
333
- execute_script('return Object.keys(sessionStorage)')
334
- end
335
-
336
- def clear_session_storage
337
- execute_script('sessionStorage.clear()')
338
- end
339
-
340
- def session_storage_size
341
- execute_script('return sessionStorage.length')
342
- end
343
-
344
- def location
345
- raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting location'
346
- end
347
-
348
- def set_location(_lat, _lon, _alt)
349
- raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting location'
350
- end
351
-
352
- def network_connection
353
- raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting network connection'
354
- end
355
-
356
- def network_connection=(_type)
357
- raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting network connection'
358
- end
359
-
360
- #
361
- # javascript execution
362
- #
363
-
364
- def execute_script(script, *args)
365
- result = execute :executeScript, {}, {script: script, args: args}
366
- unwrap_script_result result
367
- end
368
-
369
- def execute_async_script(script, *args)
370
- result = execute :executeAsyncScript, {}, {script: script, args: args}
371
- unwrap_script_result result
372
- end
373
-
374
- #
375
- # cookies
376
- #
377
-
378
- def add_cookie(cookie)
379
- execute :addCookie, {}, {cookie: cookie}
380
- end
381
-
382
- def delete_cookie(name)
383
- execute :deleteCookie, name: name
384
- end
385
-
386
- # TODO: - write specs
387
- def cookie(name)
388
- execute :getCookie, name: name
389
- end
390
-
391
- def cookies
392
- execute :getAllCookies
393
- end
394
-
395
- def delete_all_cookies
396
- cookies.each { |cookie| delete_cookie(cookie['name']) }
397
- end
398
-
399
- #
400
- # actions
401
- #
402
-
403
- def click_element(element)
404
- execute :elementClick, id: element.values.first
405
- end
406
-
407
- def click
408
- execute :click, {}, {button: 0}
409
- end
410
-
411
- def double_click
412
- execute :doubleClick
413
- end
414
-
415
- def context_click
416
- execute :click, {}, {button: 2}
417
- end
418
-
419
- def mouse_down
420
- execute :mouseDown
421
- end
422
-
423
- def mouse_up
424
- execute :mouseUp
425
- end
426
-
427
- def mouse_move_to(element, x = nil, y = nil)
428
- params = {element: element}
429
-
430
- if x && y
431
- params[:xoffset] = x
432
- params[:yoffset] = y
433
- end
434
-
435
- execute :mouseMoveTo, {}, params
436
- end
437
-
438
- def send_keys_to_active_element(keys)
439
- send_keys_to_element(active_element, keys)
440
- end
441
-
442
- # TODO: - Implement file verification
443
- def send_keys_to_element(element, keys)
444
- execute :elementSendKeys, {id: element.values.first}, {value: keys.join('').split(//)}
445
- end
446
-
447
- def clear_element(element)
448
- execute :elementClear, id: element.values.first
449
- end
450
-
451
- def submit_element(element)
452
- form = find_element_by('xpath', "./ancestor-or-self::form", element)
453
- execute_script("var e = arguments[0].ownerDocument.createEvent('Event');" \
454
- "e.initEvent('submit', true, true);" \
455
- 'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }', form.as_json)
456
- end
457
-
458
- def drag_element(element, right_by, down_by)
459
- execute :dragElement, {id: element.values.first}, {x: right_by, y: down_by}
460
- end
461
-
462
- def touch_single_tap(element)
463
- execute :touchSingleTap, {}, {element: element}
464
- end
465
-
466
- def touch_double_tap(element)
467
- execute :touchDoubleTap, {}, {element: element}
468
- end
469
-
470
- def touch_long_press(element)
471
- execute :touchLongPress, {}, {element: element}
472
- end
473
-
474
- def touch_down(x, y)
475
- execute :touchDown, {}, {x: x, y: y}
476
- end
477
-
478
- def touch_up(x, y)
479
- execute :touchUp, {}, {x: x, y: y}
480
- end
481
-
482
- def touch_move(x, y)
483
- execute :touchMove, {}, {x: x, y: y}
484
- end
485
-
486
- def touch_scroll(element, x, y)
487
- if element
488
- execute :touchScroll, {}, {element: element,
489
- xoffset: x,
490
- yoffset: y}
491
- else
492
- execute :touchScroll, {}, {xoffset: x, yoffset: y}
493
- end
494
- end
495
-
496
- def touch_flick(xspeed, yspeed)
497
- execute :touchFlick, {}, {xspeed: xspeed, yspeed: yspeed}
498
- end
499
-
500
- def touch_element_flick(element, right_by, down_by, speed)
501
- execute :touchFlick, {}, {element: element,
502
- xoffset: right_by,
503
- yoffset: down_by,
504
- speed: speed}
505
- end
506
-
507
- def screen_orientation=(orientation)
508
- execute :setScreenOrientation, {}, {orientation: orientation}
509
- end
510
-
511
- def screen_orientation
512
- execute :getScreenOrientation
513
- end
514
-
515
- #
516
- # element properties
517
- #
518
-
519
- def element_tag_name(element)
520
- execute :getElementTagName, id: element.values.first
521
- end
522
-
523
- def element_attribute(element, name)
524
- execute_atom :getAttribute, element, name
525
- end
526
-
527
- def element_property(element, name)
528
- execute :getElementProperty, id: element.values.first, name: name
529
- end
530
-
531
- def element_value(element)
532
- element_property element, 'value'
533
- end
534
-
535
- def element_text(element)
536
- execute :getElementText, id: element.values.first
537
- end
538
-
539
- def element_location(element)
540
- data = execute :getElementRect, id: element.values.first
541
-
542
- Point.new data['x'], data['y']
543
- end
544
-
545
- def element_location_once_scrolled_into_view(element)
546
- send_keys_to_element(element, [''])
547
- element_location(element)
548
- end
549
-
550
- def element_size(element)
551
- data = execute :getElementRect, id: element.values.first
552
-
553
- Dimension.new data['width'], data['height']
554
- end
555
-
556
- def element_enabled?(element)
557
- execute :isElementEnabled, id: element.values.first
558
- end
559
-
560
- def element_selected?(element)
561
- execute :isElementSelected, id: element.values.first
562
- end
563
-
564
- def element_displayed?(element)
565
- jwp = Selenium::WebDriver::Remote::Bridge::COMMANDS[:isElementDisplayed]
566
- self.class.command(:isElementDisplayed, jwp.first, jwp.last)
567
- execute :isElementDisplayed, id: element.values.first
568
- end
569
-
570
- def element_value_of_css_property(element, prop)
571
- execute :getElementCssValue, id: element.values.first, property_name: prop
572
- end
573
-
574
- #
575
- # finding elements
576
- #
577
-
578
- def active_element
579
- Element.new self, execute(:getActiveElement)
580
- end
581
-
582
- alias_method :switch_to_active_element, :active_element
583
-
584
- def find_element_by(how, what, parent = nil)
585
- how, what = convert_locators(how, what)
586
-
587
- id = if parent
588
- execute :findChildElement, {id: parent.values.first}, {using: how, value: what}
589
- else
590
- execute :findElement, {}, {using: how, value: what}
591
- end
592
- Element.new self, id
593
- end
594
-
595
- def find_elements_by(how, what, parent = nil)
596
- how, what = convert_locators(how, what)
597
-
598
- ids = if parent
599
- execute :findChildElements, {id: parent.values.first}, {using: how, value: what}
600
- else
601
- execute :findElements, {}, {using: how, value: what}
602
- end
603
-
604
- ids.map { |id| Element.new self, id }
605
- end
606
-
607
- private
608
-
609
- def convert_locators(how, what)
610
- case how
611
- when 'class name'
612
- how = 'css selector'
613
- what = ".#{escape_css(what)}"
614
- when 'id'
615
- how = 'css selector'
616
- what = "##{escape_css(what)}"
617
- when 'name'
618
- how = 'css selector'
619
- what = "*[name='#{escape_css(what)}']"
620
- when 'tag name'
621
- how = 'css selector'
622
- end
623
- [how, what]
624
- end
625
-
626
- #
627
- # executes a command on the remote server.
628
- #
629
- #
630
- # Returns the 'value' of the returned payload
631
- #
632
-
633
- def execute(*args)
634
- result = raw_execute(*args)
635
- result.payload.key?('value') ? result['value'] : result
636
- end
637
-
638
- #
639
- # executes a command on the remote server.
640
- #
641
- # @return [WebDriver::Remote::Response]
642
- #
643
-
644
- def raw_execute(command, opts = {}, command_hash = nil)
645
- verb, path = COMMANDS[command] || raise(ArgumentError, "unknown command: #{command.inspect}")
646
- path = path.dup
647
-
648
- path[':session_id'] = @session_id if path.include?(':session_id')
649
-
650
- begin
651
- opts.each do |key, value|
652
- path[key.inspect] = escaper.escape(value.to_s)
653
- end
654
- rescue IndexError
655
- raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
656
- end
657
-
658
- puts "-> #{verb.to_s.upcase} #{path}" if $DEBUG
659
- http.call verb, path, command_hash
660
- end
661
-
662
- def escaper
663
- @escaper ||= defined?(URI::Parser) ? URI::Parser.new : URI
664
- end
665
-
666
- ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]\(\)])/
667
- UNICODE_CODE_POINT = 30
668
-
669
- # Escapes invalid characters in CSS selector.
670
- # @see https://mathiasbynens.be/notes/css-escapes
671
- def escape_css(string)
672
- string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
673
- if !string.empty? && string[0] =~ /[[:digit:]]/
674
- string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..-1]}"
675
- end
676
-
677
- string
678
- end
679
- end # W3CBridge
680
- end # Remote
681
- end # WebDriver
682
- end # Selenium