selenium-webdriver 4.45.0 → 4.46.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +7 -0
  3. data/bin/linux/selenium-manager +0 -0
  4. data/bin/macos/selenium-manager +0 -0
  5. data/bin/windows/selenium-manager.exe +0 -0
  6. data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +465 -0
  7. data/lib/selenium/webdriver/bidi/protocol/browser.rb +222 -0
  8. data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +694 -0
  9. data/lib/selenium/webdriver/bidi/protocol/domain.rb +40 -0
  10. data/lib/selenium/webdriver/bidi/protocol/emulation.rb +334 -0
  11. data/lib/selenium/webdriver/bidi/protocol/input.rb +281 -0
  12. data/lib/selenium/webdriver/bidi/protocol/log.rb +104 -0
  13. data/lib/selenium/webdriver/bidi/protocol/network.rb +637 -0
  14. data/lib/selenium/webdriver/bidi/protocol/permissions.rb +73 -0
  15. data/lib/selenium/webdriver/bidi/protocol/script.rb +874 -0
  16. data/lib/selenium/webdriver/bidi/protocol/session.rb +241 -0
  17. data/lib/selenium/webdriver/bidi/protocol/speculation.rb +56 -0
  18. data/lib/selenium/webdriver/bidi/protocol/storage.rb +157 -0
  19. data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +83 -0
  20. data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +93 -0
  21. data/lib/selenium/webdriver/bidi/protocol.rb +39 -0
  22. data/lib/selenium/webdriver/bidi/serialization/record.rb +226 -0
  23. data/lib/selenium/webdriver/bidi/serialization/union.rb +114 -0
  24. data/lib/selenium/webdriver/bidi/serialization.rb +78 -0
  25. data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +936 -0
  26. data/lib/selenium/webdriver/bidi/support/check_generated.rb +55 -0
  27. data/lib/selenium/webdriver/bidi/transport.rb +52 -0
  28. data/lib/selenium/webdriver/bidi.rb +3 -0
  29. data/lib/selenium/webdriver/chrome/driver.rb +3 -3
  30. data/lib/selenium/webdriver/common/client_config.rb +97 -0
  31. data/lib/selenium/webdriver/common/driver.rb +2 -2
  32. data/lib/selenium/webdriver/common/local_driver.rb +15 -4
  33. data/lib/selenium/webdriver/common/proxy.rb +1 -1
  34. data/lib/selenium/webdriver/common.rb +1 -0
  35. data/lib/selenium/webdriver/edge/driver.rb +3 -3
  36. data/lib/selenium/webdriver/firefox/driver.rb +3 -3
  37. data/lib/selenium/webdriver/ie/driver.rb +3 -3
  38. data/lib/selenium/webdriver/remote/bidi_bridge.rb +6 -1
  39. data/lib/selenium/webdriver/remote/bridge.rb +8 -10
  40. data/lib/selenium/webdriver/remote/driver.rb +12 -4
  41. data/lib/selenium/webdriver/remote/http/common.rb +25 -12
  42. data/lib/selenium/webdriver/remote/http/default.rb +56 -39
  43. data/lib/selenium/webdriver/safari/driver.rb +3 -3
  44. data/lib/selenium/webdriver/version.rb +1 -1
  45. metadata +25 -2
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
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
+ class BiDi
23
+ module Serialization
24
+ # Immutable value type for the generated protocol classes. +Record.define(spec)+
25
+ # bakes each field's wire facts and returns a +::Data+ subclass with serialization.
26
+ #
27
+ # Cookie = Record.define(name: 'name', value: {wire_key: 'value', ref: 'Network::BytesValue'})
28
+ #
29
+ # @api private
30
+ class Record < ::Data
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)
33
+
34
+ def self.define(**spec)
35
+ extensible = spec.delete(:extensible) || false
36
+ fields = spec.map { |name, meta| field(name, meta) }
37
+ names = fields.map(&:name)
38
+ names << :extensions if extensible
39
+
40
+ klass = super(*names)
41
+ fields.freeze
42
+ # Singleton methods are inherited by `X = Record.define(…)`; ivars would not.
43
+ klass.define_singleton_method(:fields) { fields }
44
+ klass.define_singleton_method(:extensible?) { extensible }
45
+ klass.include(Serializable)
46
+ # Capture ::Data's generated +new+, then prepend (not include) Deserializer so
47
+ # its +new+ overrides it — outbound +new+ adds validation, while inbound
48
+ # +from_json+ builds directly via the captured constructor. Bound to +self+ so a
49
+ # subclass builds itself, not the base.
50
+ data_new = klass.singleton_class.instance_method(:new)
51
+ klass.singleton_class.prepend(Deserializer)
52
+ klass.define_singleton_method(:construct) { |**attributes| data_new.bind_call(self, **attributes) }
53
+ klass.singleton_class.send(:private, :construct)
54
+ klass
55
+ end
56
+
57
+ def self.field(name, meta)
58
+ meta = {wire_key: meta} if meta.is_a?(::String)
59
+ Field.new(name: name.to_sym, wire_key: meta.fetch(:wire_key, name.to_s),
60
+ nullable: meta[:nullable] || false, ref: meta[:ref],
61
+ list: meta[:list] || false, fixed: meta.fetch(:fixed, UNSET), enum: meta[:enum],
62
+ required: meta.fetch(:required, true), primitive: meta[:primitive])
63
+ end
64
+ private_class_method :field
65
+
66
+ # Inbound construction: the keyword +new+ (validated) and the wire +from_json+.
67
+ #
68
+ # @api private
69
+ module Deserializer
70
+ def new(**kwargs)
71
+ # Start from what was passed so ::Data's constructor rejects an unknown key, then fill
72
+ # each field with its value or UNSET (omitted), forcing fixed discriminators.
73
+ attributes = kwargs.dup
74
+ fields.each { |f| attributes[f.name] = fixed?(f) ? f.fixed : attributes.fetch(f.name, UNSET) }
75
+ attributes[:extensions] = kwargs.fetch(:extensions, {}) if extensible?
76
+ validate_values(attributes)
77
+ construct(**attributes)
78
+ end
79
+
80
+ # Inbound: builds from the wire. A missing required field raises (in +wire_value+),
81
+ # enum tokens are mapped back to symbols and an unrecognized one raises (in +read+), and
82
+ # extra keys are captured (extensible) or ignored (closed) — strict on shape, lenient on extras.
83
+ def from_json(json_payload)
84
+ unless json_payload.is_a?(::Hash)
85
+ raise Error::WebDriverError, "#{name} expected an object on the wire, got #{json_payload.inspect}"
86
+ end
87
+
88
+ attributes = fields.to_h do |f|
89
+ [f.name, wire_value(f, json_payload)]
90
+ end
91
+ attributes[:extensions] = extra(json_payload) if extensible?
92
+ construct(**attributes)
93
+ end
94
+
95
+ private
96
+
97
+ # Checks each field's value: a required field cannot be omitted (UNSET), a non-nullable
98
+ # field cannot be nil (nil is neither a value nor the UNSET omit-sentinel, so it would be
99
+ # silently dropped on the wire), and an enum field must be in its allowed set. The enum
100
+ # constant is resolved lazily so a cross-domain enum need not be loaded first. Outbound
101
+ # only (from +new+); inbound presence/enum are checked separately in +wire_value+/+read+.
102
+ def validate_values(attributes)
103
+ fields.each do |f|
104
+ value = attributes[f.name]
105
+ raise ::ArgumentError, "#{name}##{f.name} is required" if UNSET.equal?(value) && f.required
106
+ raise ::ArgumentError, "#{name}##{f.name} cannot be nil" if value.nil? && !f.nullable
107
+ next if value.nil? || UNSET.equal?(value)
108
+
109
+ check_outbound_shape(f, value)
110
+ Serialization.validate!("#{name}##{f.name}", value, Protocol.const_get(f.enum)) if f.enum
111
+ end
112
+ end
113
+
114
+ # Outbound mirror of check_shape: a list-typed arg must be an array, a scalar-shaped one
115
+ # (enum or ref, not a list) must not — a local ArgumentError, not a wire round-trip.
116
+ def check_outbound_shape(field, value)
117
+ return if field.list == value.is_a?(::Array)
118
+ return unless field.list || field.enum || field.ref
119
+
120
+ kind = field.list ? 'a list' : 'a single value'
121
+ raise ::ArgumentError, "#{name}##{field.name} expected #{kind}, got #{value.inspect}"
122
+ end
123
+
124
+ def fixed?(field)
125
+ !UNSET.equal?(field.fixed)
126
+ end
127
+
128
+ def wire_value(field, json_payload)
129
+ return field.fixed if fixed?(field)
130
+ return read(field, json_payload[field.wire_key]) if json_payload.key?(field.wire_key)
131
+ return UNSET unless field.required
132
+
133
+ raise Error::WebDriverError, "#{name}##{field.name} is required but was missing from the response"
134
+ end
135
+
136
+ def read(field, raw)
137
+ if raw.nil?
138
+ return raw if field.nullable
139
+
140
+ raise Error::WebDriverError, "#{name}##{field.name} received null but is not nullable"
141
+ end
142
+ check_shape(field, raw)
143
+ return Serialization.to_symbol("#{name}##{field.name}", raw, enum_hash(field)) if field.enum
144
+
145
+ if field.ref.nil?
146
+ check_primitive(field, raw) unless field.list
147
+ return raw
148
+ end
149
+
150
+ klass = (@refs ||= {})[field.name] ||= Protocol.const_get(field.ref)
151
+ field.list ? read_list(raw, klass) : klass.from_json(raw)
152
+ end
153
+
154
+ # A declared list must arrive as an array; a scalar-shaped field (enum or ref, not a
155
+ # list) must not. An opaque field carries no shape descriptor, so it passes through.
156
+ def check_shape(field, raw)
157
+ return if field.list == raw.is_a?(::Array)
158
+ return unless field.list || field.enum || field.ref
159
+
160
+ raise Error::WebDriverError,
161
+ "#{name}##{field.name} expected #{field.list ? 'a list' : 'a single value'}, got #{raw.inspect}"
162
+ end
163
+
164
+ # Ruby classes a checkable primitive admits. `number` is any Numeric (JSON has one
165
+ # number type); `integer` requires an Integer — a browser emits `5`, not `5.0`, for an
166
+ # integer (JS has no int/float split), so this rarely false-positives yet still rejects
167
+ # a genuine non-integer like 1.5. A field with no primitive descriptor is left unchecked.
168
+ PRIMITIVE_TYPES = {
169
+ 'string' => [::String], 'boolean' => [::TrueClass, ::FalseClass],
170
+ 'number' => [::Numeric], 'integer' => [::Integer]
171
+ }.freeze
172
+
173
+ def check_primitive(field, raw)
174
+ expected = PRIMITIVE_TYPES[field.primitive]
175
+ return if expected.nil? || expected.any? { |type| raw.is_a?(type) }
176
+
177
+ raise Error::WebDriverError, "#{name}##{field.name} expected #{field.primitive}, got #{raw.inspect}"
178
+ end
179
+
180
+ def enum_hash(field)
181
+ (@enums ||= {})[field.name] ||= Protocol.const_get(field.enum)
182
+ end
183
+
184
+ # Parses each element, recursing into nested lists (e.g. a map's [key, value] pairs)
185
+ # so their entries become typed too.
186
+ def read_list(raw, klass)
187
+ raw.map { |element| element.is_a?(::Array) ? read_list(element, klass) : klass.from_json(element) }
188
+ end
189
+
190
+ def extra(json_payload)
191
+ known = (@wire_keys ||= fields.map(&:wire_key))
192
+ json_payload.except(*known)
193
+ end
194
+ end
195
+
196
+ # @api private
197
+ module Serializable
198
+ def self.as_json(value)
199
+ case value
200
+ when Serializable then value.as_json
201
+ when ::Array then value.map { |element| as_json(element) }
202
+ when ::Hash then value.transform_values { |element| as_json(element) }
203
+ else value
204
+ end
205
+ end
206
+
207
+ # Omit UNSET fields; emit null only for nullable ones.
208
+ def as_json(*)
209
+ payload = {}
210
+ self.class.fields.each do |f|
211
+ value = public_send(f.name)
212
+ next if UNSET.equal?(value)
213
+ next if value.nil? && !f.nullable
214
+
215
+ value = Serialization.to_wire(value, Protocol.const_get(f.enum)) if f.enum
216
+ payload[f.wire_key] = Serializable.as_json(value)
217
+ end
218
+ payload.merge!(extensions) if self.class.extensible? && !extensions.empty?
219
+ payload
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end # BiDi
225
+ end # WebDriver
226
+ end # Selenium
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
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
+ class BiDi
23
+ module Serialization
24
+ # Resolves a wire payload to the right Data variant: a shared discriminator gives
25
+ # table dispatch; presence rules and a no-tag fallback cover unions without one.
26
+ # Subclassed (never instantiated) — each union holds its own dispatch table.
27
+ #
28
+ # class Locator < Serialization::Union
29
+ # discriminator 'type'
30
+ # variants('css' => 'BrowsingContext::CssLocator')
31
+ # end
32
+ #
33
+ # @api private
34
+ class Union
35
+ class << self
36
+ # values maps each variant's discriminator symbol to its wire token, so an
37
+ # inbound payload tag (a wire string) can be matched to the symbol-keyed table.
38
+ def discriminator(wire_key, values = {})
39
+ @discriminator = wire_key
40
+ @discriminator_values = values
41
+ end
42
+
43
+ def variants(table) = @variants = table
44
+ def presence(rules) = @presence = rules
45
+ def fallback(path) = @fallback = path
46
+
47
+ # A non-Hash payload is a bare scalar arm (e.g. input.Origin's "viewport") with
48
+ # no object to dispatch on, so it is returned unchanged.
49
+ def from_json(json_payload)
50
+ return json_payload unless json_payload.is_a?(::Hash)
51
+
52
+ variant = select(json_payload)
53
+ unless variant
54
+ raise Error::WebDriverError,
55
+ "#{name} received a variant not in this Selenium's BiDi schema: #{json_payload.inspect}"
56
+ end
57
+ Protocol.const_get(variant).from_json(json_payload)
58
+ end
59
+
60
+ # Outbound mirror of from_json: build the variant the command's kwargs describe
61
+ # so its typed as_json drives null-vs-absent per field (a flat hash through
62
+ # Transport cannot). Dispatch keys are wire names equal to their ruby kwarg
63
+ # (asserted at generation), so they match the kwargs by symbol. A mismatch here
64
+ # is a caller error (unlike an unknown inbound value), so it fails loudly.
65
+ def build(**kwargs)
66
+ variant = outbound_variant(kwargs) ||
67
+ raise(::ArgumentError, "no #{name} variant matches #{kwargs.inspect}")
68
+ klass = Protocol.const_get(variant)
69
+ # An omitted optional arrives as UNSET; forward only what was provided. A provided
70
+ # key that isn't a field of the chosen variant is an invalid combination for this union.
71
+ provided = kwargs.reject { |_, value| UNSET.equal?(value) }
72
+ invalid = provided.keys - klass.fields.map(&:name)
73
+ return klass.new(**provided) if invalid.empty?
74
+
75
+ raise ::ArgumentError, "invalid combination for #{name}: #{invalid.join(', ')}"
76
+ end
77
+
78
+ private
79
+
80
+ # The discriminator value may legitimately be null (e.g. script.NullValue's
81
+ # "null" tag), so it is matched by key presence.
82
+ def select(json_payload)
83
+ variant_for(payload_tag(json_payload)) { |k| json_payload.key?(k) }
84
+ end
85
+
86
+ # An explicit nil kwarg still counts as supplied; a non-nullable field set to nil is
87
+ # rejected at construction (Data.new), not here.
88
+ def outbound_variant(kwargs)
89
+ tag = @discriminator ? kwargs.fetch(@discriminator.to_sym, UNSET) : UNSET
90
+ variant_for(tag) { |k| kwargs.key?(k.to_sym) && !UNSET.equal?(kwargs[k.to_sym]) }
91
+ end
92
+
93
+ # The matching variant's ref, or nil when none matches (the fallback if declared).
94
+ def variant_for(tag, &supplied)
95
+ return @variants[tag] if !UNSET.equal?(tag) && @variants&.key?(tag)
96
+
97
+ @presence&.each { |path, keys| return path if keys.all?(&supplied) }
98
+ @fallback
99
+ end
100
+
101
+ # The wire tag mapped back to its variant symbol (the table's key); an
102
+ # unrecognized tag falls through as-is so select misses and from_json raises.
103
+ def payload_tag(json_payload)
104
+ return UNSET unless @discriminator && json_payload.key?(@discriminator)
105
+
106
+ wire = json_payload[@discriminator]
107
+ @discriminator_values.key(wire) || wire
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end # BiDi
113
+ end # WebDriver
114
+ end # Selenium
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
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
+ class BiDi
23
+ # Wire round-trip runtime for the generated protocol layer: the value-type bases
24
+ # (Record, Union), the omit sentinel (UNSET), and outbound enum validation.
25
+ #
26
+ # @api private
27
+ module Serialization
28
+ # Sentinel for an omitted optional: dropped from the payload entirely, vs nil which
29
+ # a nullable field serializes as wire null.
30
+ #
31
+ # @api private
32
+ UNSET = ::Object.new
33
+ def UNSET.inspect = 'UNSET'
34
+ UNSET.freeze
35
+
36
+ # Validates an outbound enum argument: +value+ is a symbol (or list of symbols) that
37
+ # must be a key of the enum hash (+{symbol => wire_token}+), so a bad value fails
38
+ # locally with a clear error instead of a round-trip. Outbound only; inbound wire
39
+ # tokens are mapped and checked separately in +to_symbol+.
40
+ #
41
+ # @api private
42
+ def self.validate!(name, value, enum)
43
+ return if UNSET.equal?(value) || value.nil?
44
+
45
+ elements = value.is_a?(::Array) ? value : [value]
46
+ invalid = elements.reject { |element| enum.key?(element) }
47
+ return if invalid.empty?
48
+
49
+ raise ::ArgumentError, "#{name} must be one of #{enum.keys.inspect}, got #{invalid.inspect}"
50
+ end
51
+
52
+ # Outbound: map a validated enum symbol (or list) to the wire token(s) to serialize.
53
+ #
54
+ # @api private
55
+ def self.to_wire(value, enum)
56
+ return value if UNSET.equal?(value) || value.nil?
57
+
58
+ value.is_a?(::Array) ? value.map { |element| enum.fetch(element) } : enum.fetch(value)
59
+ end
60
+
61
+ # Inbound: map a wire token (or list) back to its enum symbol, raising on a token
62
+ # outside our schema so a non-compliant (or newer-than-schema) browser value fails
63
+ # loud instead of silently passing through untyped.
64
+ #
65
+ # @api private
66
+ def self.to_symbol(name, value, enum)
67
+ return value if value.nil?
68
+ return value.map { |element| to_symbol(name, element, enum) } if value.is_a?(::Array)
69
+
70
+ enum.key(value) || raise(Error::WebDriverError, "#{name} received an unknown value: #{value.inspect}")
71
+ end
72
+ end
73
+ end # BiDi
74
+ end # WebDriver
75
+ end # Selenium
76
+
77
+ require 'selenium/webdriver/bidi/serialization/record'
78
+ require 'selenium/webdriver/bidi/serialization/union'