async-webdriver 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/webdriver/bridge/chrome.rb +81 -0
- data/lib/async/webdriver/bridge/firefox.rb +80 -0
- data/lib/async/webdriver/bridge/generic.rb +91 -0
- data/lib/async/webdriver/bridge/pool.rb +99 -0
- data/lib/async/webdriver/bridge/process_group.rb +77 -0
- data/lib/async/webdriver/bridge.rb +30 -0
- data/lib/async/webdriver/client.rb +71 -26
- data/lib/async/webdriver/element.rb +270 -17
- data/lib/async/webdriver/error.rb +214 -0
- data/lib/async/webdriver/locator.rb +127 -0
- data/lib/async/webdriver/request_helper.rb +120 -0
- data/lib/async/webdriver/scope/alerts.rb +40 -0
- data/lib/async/webdriver/scope/cookies.rb +43 -0
- data/lib/async/webdriver/scope/document.rb +41 -0
- data/lib/async/webdriver/scope/elements.rb +111 -0
- data/lib/async/webdriver/scope/fields.rb +66 -0
- data/lib/async/webdriver/scope/frames.rb +33 -0
- data/lib/async/webdriver/scope/navigation.rb +50 -0
- data/lib/async/webdriver/scope/printing.rb +22 -0
- data/lib/async/webdriver/scope/screen_capture.rb +23 -0
- data/lib/async/webdriver/scope/timeouts.rb +63 -0
- data/lib/async/webdriver/scope.rb +15 -0
- data/lib/async/webdriver/session.rb +107 -65
- data/lib/async/webdriver/version.rb +8 -3
- data/lib/async/webdriver/xpath.rb +29 -0
- data/lib/async/webdriver.rb +7 -12
- data/{LICENSE.txt → license.md} +6 -6
- data/readme.md +37 -0
- data.tar.gz.sig +0 -0
- metadata +71 -149
- metadata.gz.sig +0 -0
- data/.gitignore +0 -11
- data/.rspec +0 -3
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -7
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -103
- data/Guardfile +0 -45
- data/README.md +0 -3
- data/Rakefile +0 -6
- data/async-webdriver.gemspec +0 -37
- data/bin/console +0 -12
- data/bin/setup +0 -8
- data/examples/multiple_sessions.rb +0 -29
- data/lib/async/webdriver/connection.rb +0 -69
- data/lib/async/webdriver/connection_path.rb +0 -25
- data/lib/async/webdriver/execute.rb +0 -29
- data/lib/async/webdriver/session_creator.rb +0 -22
@@ -1,20 +1,273 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(json:, connection:)
|
6
|
-
@id = json.fetch "ELEMENT"
|
7
|
-
@element_connection = ConnectionPath.new "element/#{@id}", connection: connection
|
8
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
9
5
|
|
10
|
-
|
11
|
-
@element_connection.call method: "get", path: "name"
|
12
|
-
value
|
13
|
-
end
|
6
|
+
require_relative 'request_helper'
|
14
7
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
8
|
+
require_relative 'scope'
|
9
|
+
|
10
|
+
module Async
|
11
|
+
module WebDriver
|
12
|
+
# An element represents a DOM element. This class is used to interact with the DOM.
|
13
|
+
#
|
14
|
+
# ``` ruby
|
15
|
+
# element = session.find_element(:css, "main#content")
|
16
|
+
# element.click
|
17
|
+
# ```
|
18
|
+
class Element
|
19
|
+
# Attributes associated with an element.
|
20
|
+
class Attributes
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
def initialize(element)
|
24
|
+
@element = element
|
25
|
+
@keys = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get the value of an attribute.
|
29
|
+
# @parameter name [String] The name of the attribute.
|
30
|
+
# @returns [Object] The value of the attribute with the given name.
|
31
|
+
def [](name)
|
32
|
+
@element.attribute(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Set the value of an attribute.
|
36
|
+
# @parameter name [String] The name of the attribute.
|
37
|
+
# @parameter value [Object] The value of the attribute.
|
38
|
+
def []=(name, value)
|
39
|
+
@element.set_attribute(name, value)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get the names of all attributes.
|
43
|
+
def keys
|
44
|
+
@element.execute("return this.getAttributeNames()")
|
45
|
+
end
|
46
|
+
|
47
|
+
# Check if an attribute exists.
|
48
|
+
# @parameter name [String] The name of the attribute.
|
49
|
+
# @returns [Boolean] True if the attribute exists.
|
50
|
+
def key?(name)
|
51
|
+
@element.execute("return this.hasAttribute(...arguments)", name)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Iterate over all attributes.
|
55
|
+
# @yields {|name, value| ...} The name and value of each attribute.
|
56
|
+
# @parameter name [String] The name of the attribute.
|
57
|
+
# @parameter value [Object] The value of the attribute.
|
58
|
+
def each(&block)
|
59
|
+
return to_enum unless block_given?
|
60
|
+
|
61
|
+
keys.each do |key|
|
62
|
+
yield key, self[key]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Initialize the element.
|
68
|
+
# @parameter session [Session] The session the element belongs to.
|
69
|
+
# @parameter id [String] The element identifier.
|
70
|
+
def initialize(session, id)
|
71
|
+
@session = session
|
72
|
+
@delegate = session.delegate
|
73
|
+
@id = id
|
74
|
+
|
75
|
+
@attributes = nil
|
76
|
+
@properties = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# @returns [Hash] The JSON representation of the element.
|
80
|
+
def as_json
|
81
|
+
{ELEMENT_KEY => @id}
|
82
|
+
end
|
83
|
+
|
84
|
+
# @returns [String] The JSON representation of the element.
|
85
|
+
def to_json(...)
|
86
|
+
as_json.to_json(...)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @attribute [Session] The session the element belongs to.
|
90
|
+
attr :session
|
91
|
+
|
92
|
+
# @attribute [Protocol::HTTP::Middleware] The underlying HTTP client (or wrapper).
|
93
|
+
attr :delegate
|
94
|
+
|
95
|
+
# @attribute [String] The element identifier.
|
96
|
+
attr :id
|
97
|
+
|
98
|
+
# The path used for making requests to the web driver bridge.
|
99
|
+
# @parameter path [String | Nil] The path to append to the request path.
|
100
|
+
# @returns [String] The path used for making requests to the web driver bridge.
|
101
|
+
def request_path(path = nil)
|
102
|
+
if path
|
103
|
+
"/session/#{@session.id}/element/#{@id}/#{path}"
|
104
|
+
else
|
105
|
+
"/session/#{@session}/element/#{@id}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
include RequestHelper
|
110
|
+
|
111
|
+
# The current scope to use for making subsequent requests.
|
112
|
+
# @returns [Element] The element.
|
113
|
+
def current_scope
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
include Scope::Alerts
|
118
|
+
include Scope::Cookies
|
119
|
+
include Scope::Elements
|
120
|
+
include Scope::Fields
|
121
|
+
include Scope::Printing
|
122
|
+
include Scope::ScreenCapture
|
123
|
+
|
124
|
+
# Execute a script in the context of the element. `this` will be the element.
|
125
|
+
# @parameter script [String] The script to execute.
|
126
|
+
# @parameter arguments [Array] The arguments to pass to the script.
|
127
|
+
def execute(script, *arguments)
|
128
|
+
@session.execute("return (function(){#{script}}).call(...arguments)", self, *arguments)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Execute a script in the context of the element. `this` will be the element.
|
132
|
+
# @parameter script [String] The script to execute.
|
133
|
+
# @parameter arguments [Array] The arguments to pass to the script.
|
134
|
+
def execute_async(script, *arguments)
|
135
|
+
@session.execute_async("return (function(){#{script}}).call(...arguments)", self, *arguments)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get the value of an attribute.
|
139
|
+
#
|
140
|
+
# Given an attribute name, e.g. `href`, this method will return the value of the attribute, as if you had executed the following JavaScript:
|
141
|
+
#
|
142
|
+
# ```js
|
143
|
+
# element.getAttribute("href")
|
144
|
+
# ```
|
145
|
+
#
|
146
|
+
# @parameter name [String] The name of the attribute.
|
147
|
+
# @returns [Object] The value of the attribute.
|
148
|
+
def attribute(name)
|
149
|
+
get("attribute/#{name}")
|
150
|
+
end
|
151
|
+
|
152
|
+
# Set the value of an attribute.
|
153
|
+
# @parameter name [String] The name of the attribute.
|
154
|
+
# @parameter value [Object] The value of the attribute.
|
155
|
+
def set_attribute(name, value)
|
156
|
+
execute("this.setAttribute(...arguments)", name, value)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Get attributes associated with the element.
|
160
|
+
# @returns [Attributes] The attributes associated with the element.
|
161
|
+
def attributes
|
162
|
+
@attributes ||= Attributes.new(self)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Get the value of a property.
|
166
|
+
#
|
167
|
+
# Given a property name, e.g. `offsetWidth`, this method will return the value of the property, as if you had executed the following JavaScript:
|
168
|
+
#
|
169
|
+
# ```js
|
170
|
+
# element.offsetWidth
|
171
|
+
# ```
|
172
|
+
#
|
173
|
+
# @parameter name [String] The name of the property.
|
174
|
+
# @returns [Object] The value of the property.
|
175
|
+
def property(name)
|
176
|
+
get("property/#{name}")
|
177
|
+
end
|
178
|
+
|
179
|
+
# Get the value of a CSS property.
|
180
|
+
#
|
181
|
+
# Given a CSS property name, e.g. `width`, this method will return the value of the property, as if you had executed the following JavaScript:
|
182
|
+
#
|
183
|
+
# ```js
|
184
|
+
# window.getComputedStyle(element).width
|
185
|
+
# ```
|
186
|
+
#
|
187
|
+
# @parameter name [String] The name of the CSS property.
|
188
|
+
# @returns [String] The value of the CSS property.
|
189
|
+
def css(name)
|
190
|
+
get("css/#{name}")
|
191
|
+
end
|
192
|
+
|
193
|
+
# Get the text content of the element.
|
194
|
+
#
|
195
|
+
# This method will return the text content of the element, as if you had executed the following JavaScript:
|
196
|
+
#
|
197
|
+
# ```js
|
198
|
+
# element.textContent
|
199
|
+
# ```
|
200
|
+
#
|
201
|
+
# @returns [String] The text content of the element.
|
202
|
+
def text
|
203
|
+
get("text")
|
204
|
+
end
|
205
|
+
|
206
|
+
# Get the element's tag name.
|
207
|
+
#
|
208
|
+
# This method will return the tag name of the element, as if you had executed the following JavaScript:
|
209
|
+
#
|
210
|
+
# ```js
|
211
|
+
# element.tagName
|
212
|
+
# ```
|
213
|
+
#
|
214
|
+
# @returns [String] The tag name of the element.
|
215
|
+
def tag_name
|
216
|
+
get("name")
|
217
|
+
end
|
218
|
+
|
219
|
+
# A struct representing the size of an element.
|
220
|
+
Rectangle = Struct.new(:x, :y, :width, :height)
|
221
|
+
|
222
|
+
# Get the element's bounding rectangle.
|
223
|
+
# @returns [Rectangle] The element's bounding rectangle.
|
224
|
+
def rectangle
|
225
|
+
get("rect").tap do |reply|
|
226
|
+
Rectangle.new(reply["x"], reply["y"], reply["width"], reply["height"])
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Whether the element is selected OR checked.
|
231
|
+
# @returns [Boolean] True if the element is selected OR checked.
|
232
|
+
def selected?
|
233
|
+
get("selected")
|
234
|
+
end
|
235
|
+
|
236
|
+
alias checked? selected?
|
237
|
+
|
238
|
+
# Whether the element is enabled.
|
239
|
+
# @returns [Boolean] True if the element is enabled.
|
240
|
+
def enabled?
|
241
|
+
get("enabled")
|
242
|
+
end
|
243
|
+
|
244
|
+
# Whether the element is displayed.
|
245
|
+
# @returns [Boolean] True if the element is displayed.
|
246
|
+
def displayed?
|
247
|
+
get("displayed")
|
248
|
+
end
|
249
|
+
|
250
|
+
# Click the element.
|
251
|
+
def click
|
252
|
+
post("click")
|
253
|
+
end
|
254
|
+
|
255
|
+
# Clear the element.
|
256
|
+
def clear
|
257
|
+
post("clear")
|
258
|
+
end
|
259
|
+
|
260
|
+
# Send keys to the element. Simulates a user typing keys while the element is focused.
|
261
|
+
def send_keys(text)
|
262
|
+
post("value", {text: text})
|
263
|
+
end
|
264
|
+
|
265
|
+
FRAME_TAGS = ["frame", "iframe"].freeze
|
266
|
+
|
267
|
+
# Whether the element is a frame.
|
268
|
+
def frame?
|
269
|
+
FRAME_TAGS.include?(self.tag_name)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
20
273
|
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative 'version'
|
7
|
+
|
8
|
+
module Async
|
9
|
+
module WebDriver
|
10
|
+
# Error Code HTTP Status JSON Error Code Description
|
11
|
+
# element click intercepted 400 element click intercepted The Element Click command could not be completed because the element receiving the events is obscuring the element that was requested clicked.
|
12
|
+
# element not interactable 400 element not interactable A command could not be completed because the element is not pointer- or keyboard interactable.
|
13
|
+
# insecure certificate 400 insecure certificate Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate.
|
14
|
+
# invalid argument 400 invalid argument The arguments passed to a command are either invalid or malformed.
|
15
|
+
# invalid cookie domain 400 invalid cookie domain An illegal attempt was made to set a cookie under a different domain than the current page.
|
16
|
+
# invalid element state 400 invalid element state A command could not be completed because the element is in an invalid state, e.g. attempting to clear an element that isn’t both editable and resettable.
|
17
|
+
# invalid selector 400 invalid selector Argument was an invalid selector.
|
18
|
+
# invalid session id 404 invalid session id Occurs if the given session id is not in the list of active sessions, meaning the session either does not exist or that it’s not active.
|
19
|
+
# javascript error 500 javascript error An error occurred while executing JavaScript supplied by the user.
|
20
|
+
# move target out of bounds 500 move target out of bounds The target for mouse interaction is not in the browser’s viewport and cannot be brought into that viewport.
|
21
|
+
# no such alert 404 no such alert An attempt was made to operate on a modal dialog when one was not open.
|
22
|
+
# no such cookie 404 no such cookie No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document.
|
23
|
+
# no such element 404 no such element An element could not be located on the page using the given search parameters.
|
24
|
+
# no such frame 404 no such frame A command to switch to a frame could not be satisfied because the frame could not be found.
|
25
|
+
# no such window 404 no such window A command to switch to a window could not be satisfied because the window could not be found.
|
26
|
+
# no such shadow root 404 no such shadow root The element does not have a shadow root.
|
27
|
+
# script timeout error 500 script timeout A script did not complete before its timeout expired.
|
28
|
+
# session not created 500 session not created A new session could not be created.
|
29
|
+
# stale element reference 404 stale element reference A command failed because the referenced element is no longer attached to the DOM.
|
30
|
+
# detached shadow root 404 detached shadow root A command failed because the referenced shadow root is no longer attached to the DOM.
|
31
|
+
# timeout 500 timeout An operation did not complete before its timeout expired.
|
32
|
+
# unable to set cookie 500 unable to set cookie A command to set a cookie’s value could not be satisfied.
|
33
|
+
# unable to capture screen 500 unable to capture screen A screen capture was made impossible.
|
34
|
+
# unexpected alert open 500 unexpected alert open A modal dialog was open, blocking this operation.
|
35
|
+
# unknown command 404 unknown command A command could not be executed because the remote end is not aware of it.
|
36
|
+
# unknown error 500 unknown error An unknown error occurred in the remote end while processing the command.
|
37
|
+
# unknown method 405 unknown method The requested command matched a known URL but did not match any method for that URL.
|
38
|
+
# unsupported operation 500 unsupported operation Indicates that a command that should have executed properly cannot be supported for some reason.
|
39
|
+
|
40
|
+
class Error < StandardError
|
41
|
+
end
|
42
|
+
|
43
|
+
# The Element Click command could not be completed because the element receiving the events is obscuring the element that was requested clicked.
|
44
|
+
class ElementClickInterceptedError < Error
|
45
|
+
CODE = "element click intercepted"
|
46
|
+
end
|
47
|
+
|
48
|
+
# A command could not be completed because the element is not pointer- or keyboard interactable.
|
49
|
+
class ElementNotInteractableError < Error
|
50
|
+
CODE = "element not interactable"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate.
|
54
|
+
class InsecureCertificateError < Error
|
55
|
+
CODE = "insecure certificate"
|
56
|
+
end
|
57
|
+
|
58
|
+
# The arguments passed to a command are either invalid or malformed.
|
59
|
+
class InvalidArgumentError < Error
|
60
|
+
CODE = "invalid argument"
|
61
|
+
end
|
62
|
+
|
63
|
+
# An illegal attempt was made to set a cookie under a different domain than the current page.
|
64
|
+
class InvalidCookieDomainError < Error
|
65
|
+
CODE = "invalid cookie domain"
|
66
|
+
end
|
67
|
+
|
68
|
+
# A command could not be completed because the element is in an invalid state, e.g. attempting to clear an element that isn’t both editable and resettable.
|
69
|
+
class InvalidElementStateError < Error
|
70
|
+
CODE = "invalid element state"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Argument was an invalid selector.
|
74
|
+
class InvalidSelectorError < Error
|
75
|
+
CODE = "invalid selector"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Occurs if the given session id is not in the list of active sessions, meaning the session either does not exist or that it’s not active.
|
79
|
+
class InvalidSessionIdError < Error
|
80
|
+
CODE = "invalid session id"
|
81
|
+
end
|
82
|
+
|
83
|
+
# An error occurred while executing JavaScript supplied by the user.
|
84
|
+
class JavaScriptError < Error
|
85
|
+
CODE = "javascript error"
|
86
|
+
end
|
87
|
+
|
88
|
+
# The target for mouse interaction is not in the browser’s viewport and cannot be brought into that viewport.
|
89
|
+
class MoveTargetOutOfBoundsError < Error
|
90
|
+
CODE = "move target out of bounds"
|
91
|
+
end
|
92
|
+
|
93
|
+
# An attempt was made to operate on a modal dialog when one was not open.
|
94
|
+
class NoSuchAlertError < Error
|
95
|
+
CODE = "no such alert"
|
96
|
+
end
|
97
|
+
|
98
|
+
# No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document.
|
99
|
+
class NoSuchCookieError < Error
|
100
|
+
CODE = "no such cookie"
|
101
|
+
end
|
102
|
+
|
103
|
+
# An element could not be located on the page using the given search parameters.
|
104
|
+
class NoSuchElementError < Error
|
105
|
+
CODE = "no such element"
|
106
|
+
end
|
107
|
+
|
108
|
+
# A command to switch to a frame could not be satisfied because the frame could not be found.
|
109
|
+
class NoSuchFrameError < Error
|
110
|
+
CODE = "no such frame"
|
111
|
+
end
|
112
|
+
|
113
|
+
# A command to switch to a window could not be satisfied because the window could not be found.
|
114
|
+
class NoSuchWindowError < Error
|
115
|
+
CODE = "no such window"
|
116
|
+
end
|
117
|
+
|
118
|
+
# The element does not have a shadow root.
|
119
|
+
class NoSuchShadowRootError < Error
|
120
|
+
CODE = "no such shadow root"
|
121
|
+
end
|
122
|
+
|
123
|
+
# A script did not complete before its timeout expired.
|
124
|
+
class ScriptTimeoutError < Error
|
125
|
+
CODE = "script timeout error"
|
126
|
+
end
|
127
|
+
|
128
|
+
# A new session could not be created.
|
129
|
+
class SessionNotCreatedError < Error
|
130
|
+
CODE = "session not created"
|
131
|
+
end
|
132
|
+
|
133
|
+
# A command failed because the referenced element is no longer attached to the DOM.
|
134
|
+
class StaleElementReferenceError < Error
|
135
|
+
CODE = "stale element reference"
|
136
|
+
end
|
137
|
+
|
138
|
+
# A command failed because the referenced shadow root is no longer attached to the DOM.
|
139
|
+
class DetachedShadowRootError < Error
|
140
|
+
CODE = "detached shadow root"
|
141
|
+
end
|
142
|
+
|
143
|
+
# An operation did not complete before its timeout expired.
|
144
|
+
class TimeoutError < Error
|
145
|
+
CODE = "timeout"
|
146
|
+
end
|
147
|
+
|
148
|
+
# A command to set a cookie’s value could not be satisfied.
|
149
|
+
class UnableToSetCookieError < Error
|
150
|
+
CODE = "unable to set cookie"
|
151
|
+
end
|
152
|
+
|
153
|
+
# A screen capture was made impossible.
|
154
|
+
class UnableToCaptureScreenError < Error
|
155
|
+
CODE = "unable to capture screen"
|
156
|
+
end
|
157
|
+
|
158
|
+
# A modal dialog was open, blocking this operation.
|
159
|
+
class UnexpectedAlertOpenError < Error
|
160
|
+
CODE = "unexpected alert open"
|
161
|
+
end
|
162
|
+
|
163
|
+
# A command could not be executed because the remote end is not aware of it.
|
164
|
+
class UnknownCommandError < Error
|
165
|
+
CODE = "unknown command"
|
166
|
+
end
|
167
|
+
|
168
|
+
# An unknown error occurred in the remote end while processing the command.
|
169
|
+
class UnknownError < Error
|
170
|
+
CODE = "unknown error"
|
171
|
+
end
|
172
|
+
|
173
|
+
# The requested command matched a known URL but did not match any method for that URL.
|
174
|
+
class UnknownMethodError < Error
|
175
|
+
CODE = "unknown method"
|
176
|
+
end
|
177
|
+
|
178
|
+
# Indicates that a command that should have executed properly cannot be supported for some reason.
|
179
|
+
class UnsupportedOperationError < Error
|
180
|
+
CODE = "unsupported operation"
|
181
|
+
end
|
182
|
+
|
183
|
+
ERROR_CODES = {
|
184
|
+
ElementClickInterceptedError::CODE => ElementClickInterceptedError,
|
185
|
+
ElementNotInteractableError::CODE => ElementNotInteractableError,
|
186
|
+
InsecureCertificateError::CODE => InsecureCertificateError,
|
187
|
+
InvalidArgumentError::CODE => InvalidArgumentError,
|
188
|
+
InvalidCookieDomainError::CODE => InvalidCookieDomainError,
|
189
|
+
InvalidElementStateError::CODE => InvalidElementStateError,
|
190
|
+
InvalidSelectorError::CODE => InvalidSelectorError,
|
191
|
+
InvalidSessionIdError::CODE => InvalidSessionIdError,
|
192
|
+
JavaScriptError::CODE => JavaScriptError,
|
193
|
+
MoveTargetOutOfBoundsError::CODE => MoveTargetOutOfBoundsError,
|
194
|
+
NoSuchAlertError::CODE => NoSuchAlertError,
|
195
|
+
NoSuchCookieError::CODE => NoSuchCookieError,
|
196
|
+
NoSuchElementError::CODE => NoSuchElementError,
|
197
|
+
NoSuchFrameError::CODE => NoSuchFrameError,
|
198
|
+
NoSuchWindowError::CODE => NoSuchWindowError,
|
199
|
+
NoSuchShadowRootError::CODE => NoSuchShadowRootError,
|
200
|
+
ScriptTimeoutError::CODE => ScriptTimeoutError,
|
201
|
+
SessionNotCreatedError::CODE => SessionNotCreatedError,
|
202
|
+
StaleElementReferenceError::CODE => StaleElementReferenceError,
|
203
|
+
DetachedShadowRootError::CODE => DetachedShadowRootError,
|
204
|
+
TimeoutError::CODE => TimeoutError,
|
205
|
+
UnableToSetCookieError::CODE => UnableToSetCookieError,
|
206
|
+
UnableToCaptureScreenError::CODE => UnableToCaptureScreenError,
|
207
|
+
UnexpectedAlertOpenError::CODE => UnexpectedAlertOpenError,
|
208
|
+
UnknownCommandError::CODE => UnknownCommandError,
|
209
|
+
UnknownError::CODE => UnknownError,
|
210
|
+
UnknownMethodError::CODE => UnknownMethodError,
|
211
|
+
UnsupportedOperationError::CODE => UnsupportedOperationError,
|
212
|
+
}
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
module Async
|
9
|
+
module WebDriver
|
10
|
+
# A locator is used to find elements in the DOM.
|
11
|
+
#
|
12
|
+
# You can use the following convenience methods to create locators:
|
13
|
+
#
|
14
|
+
# ``` ruby
|
15
|
+
# Locator.css("main#content")
|
16
|
+
# Locator.xpath("//main[@id='content']")
|
17
|
+
# Locator.link_text("Home")
|
18
|
+
# Locator.partial_link_text("Ho")
|
19
|
+
# Locator.tag_name("main")
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# You can also use the `Locator.wrap` method to create locators from a hash:
|
23
|
+
#
|
24
|
+
# ``` ruby
|
25
|
+
# Locator.wrap(css: "main#content")
|
26
|
+
# Locator.wrap(xpath: "//main[@id='content']")
|
27
|
+
# Locator.wrap(link_text: "Home")
|
28
|
+
# Locator.wrap(partial_link_text: "Ho")
|
29
|
+
# Locator.wrap(tag_name: "main")
|
30
|
+
# ```
|
31
|
+
#
|
32
|
+
# For more information, see: <https://w3c.github.io/webdriver/#locator-strategies>.
|
33
|
+
class Locator
|
34
|
+
# A convenience wrapper for specifying locators.
|
35
|
+
#
|
36
|
+
# You may provide either:
|
37
|
+
# 1. A locator instance, or
|
38
|
+
# 2. A single option `css:`, `xpath:`, `link_text:`, `partial_link_text:` or `tag_name:`, or
|
39
|
+
# 3. A `using:` and `value:` option which will be used directly.
|
40
|
+
#
|
41
|
+
# @parameter locator [Locator] A locator to use directly.
|
42
|
+
# @option css [String] A CSS selector.
|
43
|
+
# @option xpath [String] An XPath expression.
|
44
|
+
# @option link_text [String] The exact text of a link.
|
45
|
+
# @option partial_link_text [String] A partial match for the text of a link.
|
46
|
+
# @option tag_name [String] The name of a tag.
|
47
|
+
# @option using [String] The locator strategy to use.
|
48
|
+
# @option value [String] The value to use with the locator strategy.
|
49
|
+
def self.wrap(locator = nil, **options)
|
50
|
+
if locator.is_a?(Locator)
|
51
|
+
locator
|
52
|
+
elsif css = options[:css]
|
53
|
+
css(css)
|
54
|
+
elsif xpath = options[:xpath]
|
55
|
+
xpath(xpath)
|
56
|
+
elsif link_text = options[:link_text]
|
57
|
+
link_text(link_text)
|
58
|
+
elsif partial_link_text = options[:partial_link_text]
|
59
|
+
partial_link_text(partial_link_text)
|
60
|
+
elsif tag_name = options[:tag_name]
|
61
|
+
tag_name(tag_name)
|
62
|
+
elsif using = options[:using]
|
63
|
+
new(using, options[:value])
|
64
|
+
else
|
65
|
+
raise ArgumentError, "Unable to interpret #{locator.inspect} with #{options.inspect}!"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# A convenience wrapper for specifying CSS locators.
|
70
|
+
def self.css(css)
|
71
|
+
new("css selector", css)
|
72
|
+
end
|
73
|
+
|
74
|
+
# A convenience wrapper for specifying link text locators.
|
75
|
+
def self.link_text(text)
|
76
|
+
new("link text", text)
|
77
|
+
end
|
78
|
+
|
79
|
+
# A convenience wrapper for specifying partial link text locators.
|
80
|
+
def self.partial_link_text(text)
|
81
|
+
new("partial link text", text)
|
82
|
+
end
|
83
|
+
|
84
|
+
# A convenience wrapper for specifying tag name locators.
|
85
|
+
def self.tag_name(name)
|
86
|
+
new("tag name", name)
|
87
|
+
end
|
88
|
+
|
89
|
+
# A convenience wrapper for specifying XPath locators.
|
90
|
+
def self.xpath(xpath)
|
91
|
+
new("xpath", xpath)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Initialize the locator.
|
95
|
+
#
|
96
|
+
# A locator strategy must usually be one of the following:
|
97
|
+
# - `css selector`: Used to find elements via CSS selectors.
|
98
|
+
# - `link text`: Used to find anchor elements by their link text.
|
99
|
+
# - `partial link text`: Used to find anchor elements by their partial link text.
|
100
|
+
# - `tag name`: Used to find elements by their tag name.
|
101
|
+
# - `xpath`: Used to find elements via XPath expressions.
|
102
|
+
#
|
103
|
+
# @parameter using [String] The locator strategy to use.
|
104
|
+
# @parameter value [String] The value to use with the locator strategy.
|
105
|
+
def initialize(using, value)
|
106
|
+
@using = using
|
107
|
+
@value = value
|
108
|
+
end
|
109
|
+
|
110
|
+
# @attribute [String] The locator strategy to use.
|
111
|
+
attr :using
|
112
|
+
|
113
|
+
# @attribute [String] The value to use with the locator strategy.
|
114
|
+
attr :value
|
115
|
+
|
116
|
+
# @returns [Hash] A JSON representation of the locator.
|
117
|
+
def as_json
|
118
|
+
{using: @using, value: @value}
|
119
|
+
end
|
120
|
+
|
121
|
+
# @returns [String] A JSON representation of the locator.
|
122
|
+
def to_json(...)
|
123
|
+
as_json.to_json(...)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|