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

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 (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