selenium-webdriver 4.46.0 → 4.47.0
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.
- checksums.yaml +4 -4
- data/CHANGES +29 -0
- data/bin/linux/selenium-manager +0 -0
- data/bin/macos/selenium-manager +0 -0
- data/bin/selenium-manager-THIRD-PARTY-NOTICES.txt +5971 -0
- data/bin/selenium-manager.cdx.json +11629 -0
- data/bin/windows/selenium-manager.exe +0 -0
- data/lib/selenium/webdriver/{common/driver_extensions/has_bidi.rb → bidi/error.rb} +21 -13
- data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +53 -23
- data/lib/selenium/webdriver/bidi/protocol/browser.rb +43 -15
- data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +172 -68
- data/lib/selenium/webdriver/bidi/protocol/domain.rb +8 -2
- data/lib/selenium/webdriver/bidi/protocol/emulation.rb +123 -9
- data/lib/selenium/webdriver/bidi/protocol/error_code.rb +66 -0
- data/lib/selenium/webdriver/bidi/protocol/input.rb +101 -28
- data/lib/selenium/webdriver/bidi/protocol/log.rb +17 -6
- data/lib/selenium/webdriver/bidi/protocol/network.rb +146 -63
- data/lib/selenium/webdriver/bidi/protocol/permissions.rb +4 -2
- data/lib/selenium/webdriver/bidi/protocol/script.rb +264 -86
- data/lib/selenium/webdriver/bidi/protocol/session.rb +48 -3
- data/lib/selenium/webdriver/bidi/protocol/speculation.rb +4 -2
- data/lib/selenium/webdriver/bidi/protocol/storage.rb +32 -6
- data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +7 -2
- data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +49 -10
- data/lib/selenium/webdriver/bidi/protocol.rb +3 -0
- data/lib/selenium/webdriver/bidi/serialization/record.rb +194 -18
- data/lib/selenium/webdriver/bidi/serialization/union.rb +45 -3
- data/lib/selenium/webdriver/bidi/serialization.rb +13 -1
- data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +529 -45
- data/lib/selenium/webdriver/bidi/support/check_generated.rb +12 -4
- data/lib/selenium/webdriver/bidi/transport.rb +8 -3
- data/lib/selenium/webdriver/chrome/service.rb +4 -1
- data/lib/selenium/webdriver/chromium/driver.rb +0 -1
- data/lib/selenium/webdriver/chromium/options.rb +20 -0
- data/lib/selenium/webdriver/common/driver.rb +11 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +0 -7
- data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +0 -7
- data/lib/selenium/webdriver/common/driver_finder.rb +4 -3
- data/lib/selenium/webdriver/common/options.rb +21 -1
- data/lib/selenium/webdriver/common/proxy.rb +0 -8
- data/lib/selenium/webdriver/common/selenium_manager.rb +2 -1
- data/lib/selenium/webdriver/common/takes_screenshot.rb +1 -1
- data/lib/selenium/webdriver/common.rb +0 -1
- data/lib/selenium/webdriver/edge/service.rb +4 -1
- data/lib/selenium/webdriver/firefox/driver.rb +0 -3
- data/lib/selenium/webdriver/firefox/options.rb +19 -0
- data/lib/selenium/webdriver/firefox/profile.rb +13 -6
- data/lib/selenium/webdriver/remote/bidi_bridge.rb +39 -12
- data/lib/selenium/webdriver/remote/bridge.rb +1 -1
- data/lib/selenium/webdriver/remote/driver.rb +3 -1
- data/lib/selenium/webdriver/remote/http/default.rb +1 -1
- data/lib/selenium/webdriver/safari/options.rb +16 -1
- data/lib/selenium/webdriver/support/guards/guard.rb +20 -3
- data/lib/selenium/webdriver/support/guards.rb +9 -3
- data/lib/selenium/webdriver/version.rb +1 -1
- metadata +6 -3
|
@@ -29,7 +29,8 @@ module Selenium
|
|
|
29
29
|
# @api private
|
|
30
30
|
class Record < ::Data
|
|
31
31
|
# Named Field, not Member, to avoid colliding with +::Data#members+.
|
|
32
|
-
Field = ::Data.define(:name, :wire_key, :nullable, :ref, :list, :fixed, :enum, :required, :primitive
|
|
32
|
+
Field = ::Data.define(:name, :wire_key, :nullable, :ref, :list, :fixed, :enum, :required, :primitive,
|
|
33
|
+
:scalar, :const)
|
|
33
34
|
|
|
34
35
|
def self.define(**spec)
|
|
35
36
|
extensible = spec.delete(:extensible) || false
|
|
@@ -59,7 +60,8 @@ module Selenium
|
|
|
59
60
|
Field.new(name: name.to_sym, wire_key: meta.fetch(:wire_key, name.to_s),
|
|
60
61
|
nullable: meta[:nullable] || false, ref: meta[:ref],
|
|
61
62
|
list: meta[:list] || false, fixed: meta.fetch(:fixed, UNSET), enum: meta[:enum],
|
|
62
|
-
required: meta.fetch(:required, true), primitive: meta[:primitive]
|
|
63
|
+
required: meta.fetch(:required, true), primitive: meta[:primitive],
|
|
64
|
+
scalar: meta[:scalar], const: meta.fetch(:const, UNSET))
|
|
63
65
|
end
|
|
64
66
|
private_class_method :field
|
|
65
67
|
|
|
@@ -77,9 +79,10 @@ module Selenium
|
|
|
77
79
|
construct(**attributes)
|
|
78
80
|
end
|
|
79
81
|
|
|
80
|
-
# Inbound: builds from the wire. A missing required field
|
|
81
|
-
# enum tokens are mapped back to symbols and an
|
|
82
|
-
#
|
|
82
|
+
# Inbound: builds from the wire. A missing required field is omitted and warned (or
|
|
83
|
+
# raised in strict mode, in +wire_value+); enum tokens are mapped back to symbols and an
|
|
84
|
+
# unrecognized one raises (in +read+); an undeclared property is captured silently
|
|
85
|
+
# (extensible) or warned and dropped (closed) — strict on shape, lenient on extras.
|
|
83
86
|
def from_json(json_payload)
|
|
84
87
|
unless json_payload.is_a?(::Hash)
|
|
85
88
|
raise Error::WebDriverError, "#{name} expected an object on the wire, got #{json_payload.inspect}"
|
|
@@ -88,7 +91,12 @@ module Selenium
|
|
|
88
91
|
attributes = fields.to_h do |f|
|
|
89
92
|
[f.name, wire_value(f, json_payload)]
|
|
90
93
|
end
|
|
91
|
-
|
|
94
|
+
undeclared = extra(json_payload)
|
|
95
|
+
if extensible?
|
|
96
|
+
attributes[:extensions] = undeclared # the spec sanctions these extras; preserve them silently
|
|
97
|
+
else
|
|
98
|
+
warn_undeclared(undeclared) unless undeclared.empty?
|
|
99
|
+
end
|
|
92
100
|
construct(**attributes)
|
|
93
101
|
end
|
|
94
102
|
|
|
@@ -96,9 +104,11 @@ module Selenium
|
|
|
96
104
|
|
|
97
105
|
# Checks each field's value: a required field cannot be omitted (UNSET), a non-nullable
|
|
98
106
|
# field cannot be nil (nil is neither a value nor the UNSET omit-sentinel, so it would be
|
|
99
|
-
# silently dropped on the wire),
|
|
100
|
-
#
|
|
101
|
-
#
|
|
107
|
+
# silently dropped on the wire), a nullable-const field must carry its literal (not some
|
|
108
|
+
# other value), a primitive field must be the matching Ruby type, and an enum field must be
|
|
109
|
+
# in its allowed set. The enum constant is resolved lazily so a cross-domain enum need not be
|
|
110
|
+
# loaded first. Outbound only (from +new+); inbound presence/primitive/enum are checked
|
|
111
|
+
# separately in +wire_value+/+read+.
|
|
102
112
|
def validate_values(attributes)
|
|
103
113
|
fields.each do |f|
|
|
104
114
|
value = attributes[f.name]
|
|
@@ -106,11 +116,79 @@ module Selenium
|
|
|
106
116
|
raise ::ArgumentError, "#{name}##{f.name} cannot be nil" if value.nil? && !f.nullable
|
|
107
117
|
next if value.nil? || UNSET.equal?(value)
|
|
108
118
|
|
|
109
|
-
|
|
110
|
-
Serialization.validate!("#{name}##{f.name}", value, Protocol.const_get(f.enum)) if f.enum
|
|
119
|
+
validate_present(f, value)
|
|
111
120
|
end
|
|
112
121
|
end
|
|
113
122
|
|
|
123
|
+
# Checks a field that carries an actual value (neither omitted nor nil): a nullable-const
|
|
124
|
+
# field against its literal, list/scalar shape, primitive type (lists excepted, as inbound
|
|
125
|
+
# does), ref type, and enum membership (resolved lazily so a cross-domain enum need not load first).
|
|
126
|
+
def validate_present(field, value)
|
|
127
|
+
validate_const(field, value)
|
|
128
|
+
check_outbound_shape(field, value)
|
|
129
|
+
check_outbound_primitive(field, value) unless field.list
|
|
130
|
+
validate_ref(field, value) if field.ref
|
|
131
|
+
Serialization.validate!("#{name}##{field.name}", value, Protocol.const_get(field.enum)) if field.enum
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Outbound mirror of read_ref: a ref-typed value must be the type it declares, so a wrong
|
|
135
|
+
# record or a value no union variant accepts is a caller error caught here, not a browser
|
|
136
|
+
# round-trip. Shape is already checked, so a list is an Array.
|
|
137
|
+
def validate_ref(field, value)
|
|
138
|
+
klass = (@refs ||= {})[field.name] ||= Protocol.const_get(field.ref)
|
|
139
|
+
field.list ? validate_ref_list(field, klass, value) : validate_ref_value(field, klass, value)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Mirrors read_list: a scalar field is a [key, value] map, a nested list recurses, otherwise
|
|
143
|
+
# each element is checked against the ref.
|
|
144
|
+
def validate_ref_list(field, klass, list)
|
|
145
|
+
list.each do |element|
|
|
146
|
+
if field.scalar
|
|
147
|
+
validate_ref_entry(field, klass, element)
|
|
148
|
+
elsif element.is_a?(::Array)
|
|
149
|
+
validate_ref_list(field, klass, element)
|
|
150
|
+
else
|
|
151
|
+
validate_ref_value(field, klass, element)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# A [key, value] map entry: the key may be a variant or a bare scalar, the value is a variant.
|
|
157
|
+
def validate_ref_entry(field, klass, element)
|
|
158
|
+
unless element.is_a?(::Array) && element.size == 2
|
|
159
|
+
raise ::ArgumentError, "#{name}##{field.name} expected a [key, value] pair, got #{element.inspect}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
key, value = element
|
|
163
|
+
key.is_a?(Serializable) ? validate_ref_value(field, klass, key) : check_outbound_scalar(field, key)
|
|
164
|
+
validate_ref_value(field, klass, value)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# A record ref must be an instance of that record; a union ref must be one the union accepts.
|
|
168
|
+
def validate_ref_value(field, klass, value)
|
|
169
|
+
return if klass < Union ? klass.valid_outbound?(value) : value.is_a?(klass)
|
|
170
|
+
|
|
171
|
+
raise ::ArgumentError, "#{name}##{field.name} expected #{field.ref}, got #{value.inspect}"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Outbound mirror of scalar_value: a bare map key must match one of the arm's primitives.
|
|
175
|
+
def check_outbound_scalar(field, value)
|
|
176
|
+
expected = Array(field.scalar).flat_map { |primitive| PRIMITIVE_TYPES[primitive] || [] }
|
|
177
|
+
return if expected.empty? || expected.any? { |type| value.is_a?(type) }
|
|
178
|
+
|
|
179
|
+
raise ::ArgumentError,
|
|
180
|
+
"#{name}##{field.name} expected #{Array(field.scalar).join(' or ')}, got #{value.inspect}"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# A nullable constant (`literal / null`) is caller-settable but its only non-null value is
|
|
184
|
+
# the literal, so a value that is neither the literal nor nil (nil is handled above) is a
|
|
185
|
+
# local error rather than a wire round-trip. A non-const field carries UNSET here and passes.
|
|
186
|
+
def validate_const(field, value)
|
|
187
|
+
return if UNSET.equal?(field.const) || value == field.const
|
|
188
|
+
|
|
189
|
+
raise ::ArgumentError, "#{name}##{field.name} must be #{field.const.inspect}, got #{value.inspect}"
|
|
190
|
+
end
|
|
191
|
+
|
|
114
192
|
# Outbound mirror of check_shape: a list-typed arg must be an array, a scalar-shaped one
|
|
115
193
|
# (enum or ref, not a list) must not — a local ArgumentError, not a wire round-trip.
|
|
116
194
|
def check_outbound_shape(field, value)
|
|
@@ -121,6 +199,17 @@ module Selenium
|
|
|
121
199
|
raise ::ArgumentError, "#{name}##{field.name} expected #{kind}, got #{value.inspect}"
|
|
122
200
|
end
|
|
123
201
|
|
|
202
|
+
# Outbound mirror of check_primitive: a primitive-typed arg (`string`/`integer`/…) must be
|
|
203
|
+
# the matching Ruby type, so a caller mistake (a string width, a float count) is a local
|
|
204
|
+
# ArgumentError here rather than a rejection the browser reports a round-trip later. A field
|
|
205
|
+
# with no primitive descriptor (enum, ref, opaque) passes; lists are skipped, as inbound does.
|
|
206
|
+
def check_outbound_primitive(field, value)
|
|
207
|
+
expected = PRIMITIVE_TYPES[field.primitive]
|
|
208
|
+
return if expected.nil? || expected.any? { |type| value.is_a?(type) }
|
|
209
|
+
|
|
210
|
+
raise ::ArgumentError, "#{name}##{field.name} expected #{field.primitive}, got #{value.inspect}"
|
|
211
|
+
end
|
|
212
|
+
|
|
124
213
|
def fixed?(field)
|
|
125
214
|
!UNSET.equal?(field.fixed)
|
|
126
215
|
end
|
|
@@ -130,7 +219,19 @@ module Selenium
|
|
|
130
219
|
return read(field, json_payload[field.wire_key]) if json_payload.key?(field.wire_key)
|
|
131
220
|
return UNSET unless field.required
|
|
132
221
|
|
|
133
|
-
|
|
222
|
+
missing_required(field)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# A required field absent from the response is tolerated as omitted (UNSET) and warned, so a
|
|
226
|
+
# schema ahead of the browser does not block the caller; strict mode (SE_BIDI_STRICT) escalates
|
|
227
|
+
# to an error for callers who want it. Omitted (UNSET) stays distinct from an explicit null (nil),
|
|
228
|
+
# which matters for the required-and-nullable fields the schema flags.
|
|
229
|
+
def missing_required(field)
|
|
230
|
+
message = "#{name}##{field.name} is required but was missing from the response"
|
|
231
|
+
raise Error::WebDriverError, message if Serialization.strict?
|
|
232
|
+
|
|
233
|
+
WebDriver.logger.warn(message, id: :bidi_missing_required)
|
|
234
|
+
UNSET
|
|
134
235
|
end
|
|
135
236
|
|
|
136
237
|
def read(field, raw)
|
|
@@ -147,8 +248,18 @@ module Selenium
|
|
|
147
248
|
return raw
|
|
148
249
|
end
|
|
149
250
|
|
|
251
|
+
read_ref(field, raw)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Reads a ref-typed value into its class. A `scalar` position is an inline union with
|
|
255
|
+
# a scalar arm collapsed onto its union ref (a map's string keys): a non-object leaf
|
|
256
|
+
# passes through instead of being handed to the object_only union, but only when it
|
|
257
|
+
# matches the arm's primitive (+scalar+ carries it). A list recurses per element.
|
|
258
|
+
def read_ref(field, raw)
|
|
150
259
|
klass = (@refs ||= {})[field.name] ||= Protocol.const_get(field.ref)
|
|
151
|
-
|
|
260
|
+
return read_list(field, raw, klass) if field.list
|
|
261
|
+
|
|
262
|
+
field.scalar && !raw.is_a?(::Hash) ? scalar_value(field, raw) : klass.from_json(raw)
|
|
152
263
|
end
|
|
153
264
|
|
|
154
265
|
# A declared list must arrive as an array; a scalar-shaped field (enum or ref, not a
|
|
@@ -181,16 +292,64 @@ module Selenium
|
|
|
181
292
|
(@enums ||= {})[field.name] ||= Protocol.const_get(field.enum)
|
|
182
293
|
end
|
|
183
294
|
|
|
184
|
-
# Parses each element
|
|
185
|
-
#
|
|
186
|
-
|
|
187
|
-
|
|
295
|
+
# Parses each element. A `scalar` field is a map encoded as `[key, value]` pairs, so
|
|
296
|
+
# every element must be a 2-item pair — each is read as one, and a malformed entry is
|
|
297
|
+
# rejected. Non-scalar lists recurse into nested lists; other elements deserialize.
|
|
298
|
+
def read_list(field, raw, klass)
|
|
299
|
+
raw.map do |element|
|
|
300
|
+
if field.scalar
|
|
301
|
+
read_map_entry(field, element, klass)
|
|
302
|
+
elsif element.is_a?(::Array)
|
|
303
|
+
read_list(field, element, klass)
|
|
304
|
+
else
|
|
305
|
+
klass.from_json(element)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# A map entry is a `[key, value]` pair. The key is `Ref / text` — an object key
|
|
311
|
+
# deserializes, a bare-string key passes through once validated against the arm's
|
|
312
|
+
# primitive. The value is the object-only Ref and always deserializes, so a bare scalar
|
|
313
|
+
# there is rejected (object_only holds at the value position). A non-pair element is a
|
|
314
|
+
# malformed entry and is rejected outright.
|
|
315
|
+
def read_map_entry(field, element, klass)
|
|
316
|
+
unless element.is_a?(::Array) && element.size == 2
|
|
317
|
+
raise Error::WebDriverError,
|
|
318
|
+
"#{name}##{field.name} expected a [key, value] pair, got #{element.inspect}"
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
key, value = element
|
|
322
|
+
key = key.is_a?(::Hash) ? klass.from_json(key) : scalar_value(field, key)
|
|
323
|
+
[key, klass.from_json(value)]
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# A bare scalar at a scalar-tolerant union position must match one of the union's
|
|
327
|
+
# scalar-arm primitives (+scalar+ is a primitive name or an array of them); a
|
|
328
|
+
# wrong-typed scalar (a number where a string is expected) is a wire error, not
|
|
329
|
+
# something to pass through. An unrecognized primitive (none in PRIMITIVE_TYPES) is
|
|
330
|
+
# left unchecked, matching the lenient default elsewhere.
|
|
331
|
+
def scalar_value(field, value)
|
|
332
|
+
expected = Array(field.scalar).flat_map { |primitive| PRIMITIVE_TYPES[primitive] || [] }
|
|
333
|
+
return value if expected.empty? || expected.any? { |type| value.is_a?(type) }
|
|
334
|
+
|
|
335
|
+
raise Error::WebDriverError,
|
|
336
|
+
"#{name}##{field.name} expected #{Array(field.scalar).join(' or ')}, got #{value.inspect}"
|
|
188
337
|
end
|
|
189
338
|
|
|
190
339
|
def extra(json_payload)
|
|
191
340
|
known = (@wire_keys ||= fields.map(&:wire_key))
|
|
192
341
|
json_payload.except(*known)
|
|
193
342
|
end
|
|
343
|
+
|
|
344
|
+
# Forward-compat signal: a property a closed type does not model is dropped and warned so
|
|
345
|
+
# schema drift is visible (an extensible type keeps its extras silently — the spec sanctions
|
|
346
|
+
# them). Tagged +:bidi_undeclared_property+ so a caller can silence it via +logger.ignore+.
|
|
347
|
+
def warn_undeclared(undeclared)
|
|
348
|
+
undeclared.each_key do |key|
|
|
349
|
+
WebDriver.logger.warn("#{name} received an undeclared property: #{key.inspect}",
|
|
350
|
+
id: :bidi_undeclared_property)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
194
353
|
end
|
|
195
354
|
|
|
196
355
|
# @api private
|
|
@@ -215,9 +374,26 @@ module Selenium
|
|
|
215
374
|
value = Serialization.to_wire(value, Protocol.const_get(f.enum)) if f.enum
|
|
216
375
|
payload[f.wire_key] = Serializable.as_json(value)
|
|
217
376
|
end
|
|
218
|
-
|
|
377
|
+
merge_extensions!(payload) if self.class.extensible? && !extensions.empty?
|
|
219
378
|
payload
|
|
220
379
|
end
|
|
380
|
+
|
|
381
|
+
private
|
|
382
|
+
|
|
383
|
+
# Merge the passthrough extras onto the wire, erroring rather than letting an extra whose key
|
|
384
|
+
# is a declared field's wire key silently clobber that typed value; an extra is by definition
|
|
385
|
+
# a field the spec does not declare. Keys are stringified first so a symbol key (e.g. `name:`)
|
|
386
|
+
# cannot slip past the guard and then reappear as a duplicate wire key once serialized. The
|
|
387
|
+
# single gate every outbound path funnels through: +new+, +with+, and in-place mutation.
|
|
388
|
+
def merge_extensions!(payload)
|
|
389
|
+
extras = extensions.transform_keys(&:to_s)
|
|
390
|
+
collisions = extras.keys & self.class.fields.map(&:wire_key)
|
|
391
|
+
unless collisions.empty?
|
|
392
|
+
raise ::ArgumentError, "#{self.class.name} extensions shadow declared fields: #{collisions.join(', ')}"
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
payload.merge!(extras)
|
|
396
|
+
end
|
|
221
397
|
end
|
|
222
398
|
end
|
|
223
399
|
end
|
|
@@ -44,10 +44,24 @@ module Selenium
|
|
|
44
44
|
def presence(rules) = @presence = rules
|
|
45
45
|
def fallback(path) = @fallback = path
|
|
46
46
|
|
|
47
|
-
#
|
|
48
|
-
#
|
|
47
|
+
# Declared (via the schema's `objectOnly` signal) on a union whose every arm is an
|
|
48
|
+
# object, so a non-Hash payload is a schema violation rather than a scalar arm.
|
|
49
|
+
def object_only = @object_only = true
|
|
50
|
+
|
|
51
|
+
# Declared (via the schema's `scalarValues` signal) on a non-object_only union whose
|
|
52
|
+
# bare-scalar arms are a fixed set of literals (input.Origin's "viewport" / "pointer").
|
|
53
|
+
# An outbound scalar outside that set matches no arm, so it is a caller error.
|
|
54
|
+
def scalar_values(*values) = @scalar_values = values
|
|
55
|
+
|
|
56
|
+
# A non-Hash payload is a bare scalar arm (e.g. input.Origin's "viewport") with no
|
|
57
|
+
# object to dispatch on, so it is returned unchanged — unless every arm is an object
|
|
58
|
+
# (object_only), where a non-Hash cannot match any variant and is a wire error.
|
|
49
59
|
def from_json(json_payload)
|
|
50
|
-
|
|
60
|
+
unless json_payload.is_a?(::Hash)
|
|
61
|
+
return json_payload unless @object_only
|
|
62
|
+
|
|
63
|
+
raise Error::WebDriverError, "#{name} expected an object on the wire, got #{json_payload.inspect}"
|
|
64
|
+
end
|
|
51
65
|
|
|
52
66
|
variant = select(json_payload)
|
|
53
67
|
unless variant
|
|
@@ -75,8 +89,36 @@ module Selenium
|
|
|
75
89
|
raise ::ArgumentError, "invalid combination for #{name}: #{invalid.join(', ')}"
|
|
76
90
|
end
|
|
77
91
|
|
|
92
|
+
# Outbound mirror of from_json: is +value+ one this union accepts? Any variant is accepted,
|
|
93
|
+
# and a variant that is itself a union recurses (e.g. LocalValue's RemoteReference fallback).
|
|
94
|
+
# A non-object_only union (e.g. input.Origin) also admits one of its pinned bare-scalar
|
|
95
|
+
# literals; an object (a Hash or another union's record) that matched no variant does not.
|
|
96
|
+
def valid_outbound?(value)
|
|
97
|
+
return true if variant_refs.any? { |ref| variant_accepts?(ref, value) }
|
|
98
|
+
|
|
99
|
+
!@object_only && scalar_arm?(value)
|
|
100
|
+
end
|
|
101
|
+
|
|
78
102
|
private
|
|
79
103
|
|
|
104
|
+
# A bare-scalar arm must be one of the literals the schema pinned for this union
|
|
105
|
+
# (scalar_values, e.g. input.Origin's "viewport" / "pointer"). The generator guarantees a
|
|
106
|
+
# non-object_only union declares them, so no runtime guard is needed here.
|
|
107
|
+
def scalar_arm?(value)
|
|
108
|
+
@scalar_values.include?(value)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Every variant's class name: the discriminated table, the presence paths, and the fallback.
|
|
112
|
+
def variant_refs
|
|
113
|
+
@variant_refs ||= [*@variants&.values, *@presence&.keys, @fallback].compact
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# A variant that is itself a union recurses; a record variant is matched by instance.
|
|
117
|
+
def variant_accepts?(ref, value)
|
|
118
|
+
klass = (@variant_classes ||= {})[ref] ||= Protocol.const_get(ref)
|
|
119
|
+
klass < Union ? klass.valid_outbound?(value) : value.is_a?(klass)
|
|
120
|
+
end
|
|
121
|
+
|
|
80
122
|
# The discriminator value may legitimately be null (e.g. script.NullValue's
|
|
81
123
|
# "null" tag), so it is matched by key presence.
|
|
82
124
|
def select(json_payload)
|
|
@@ -21,7 +21,8 @@ module Selenium
|
|
|
21
21
|
module WebDriver
|
|
22
22
|
class BiDi
|
|
23
23
|
# Wire round-trip runtime for the generated protocol layer: the value-type bases
|
|
24
|
-
# (Record, Union), the omit sentinel (UNSET),
|
|
24
|
+
# (Record, Union), the omit sentinel (UNSET), outbound enum validation, and the
|
|
25
|
+
# strict-inbound toggle.
|
|
25
26
|
#
|
|
26
27
|
# @api private
|
|
27
28
|
module Serialization
|
|
@@ -33,6 +34,17 @@ module Selenium
|
|
|
33
34
|
def UNSET.inspect = 'UNSET'
|
|
34
35
|
UNSET.freeze
|
|
35
36
|
|
|
37
|
+
# Strict inbound mode. Off by default: a required field missing from a response is
|
|
38
|
+
# tolerated as omitted and warned, so a schema ahead of the browser does not block the
|
|
39
|
+
# caller. When SE_BIDI_STRICT is set to anything but 0/false, that same case escalates
|
|
40
|
+
# to an error for callers who want it.
|
|
41
|
+
#
|
|
42
|
+
# @api private
|
|
43
|
+
def self.strict?
|
|
44
|
+
value = ENV.fetch('SE_BIDI_STRICT', '').strip.downcase
|
|
45
|
+
!value.empty? && value != '0' && value != 'false'
|
|
46
|
+
end
|
|
47
|
+
|
|
36
48
|
# Validates an outbound enum argument: +value+ is a symbol (or list of symbols) that
|
|
37
49
|
# must be a key of the enum hash (+{symbol => wire_token}+), so a bad value fails
|
|
38
50
|
# locally with a clear error instead of a round-trip. Outbound only; inbound wire
|