selenium-webdriver 4.35.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 +111 -0
- data/Gemfile +1 -4
- data/LICENSE +1 -1
- data/NOTICE +1 -1
- data/README.md +43 -23
- 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/server.rb +30 -5
- data/lib/selenium/webdriver/atoms/findElements.js +272 -54
- data/lib/selenium/webdriver/atoms/getAttribute.js +152 -6
- data/lib/selenium/webdriver/atoms/isDisplayed.js +385 -28
- data/lib/selenium/webdriver/bidi/browser.rb +71 -0
- data/lib/selenium/webdriver/bidi/browsing_context.rb +3 -2
- data/lib/selenium/webdriver/bidi/{log/filter_by.rb → error.rb} +20 -16
- data/lib/selenium/webdriver/bidi/log_handler.rb +5 -0
- data/lib/selenium/webdriver/bidi/network/cookies.rb +13 -9
- data/lib/selenium/webdriver/bidi/network/credentials.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/headers.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/intercepted_auth.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/intercepted_item.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/intercepted_request.rb +20 -4
- data/lib/selenium/webdriver/bidi/network/intercepted_response.rb +24 -6
- data/lib/selenium/webdriver/bidi/network/url_pattern.rb +4 -0
- data/lib/selenium/webdriver/bidi/network.rb +18 -9
- data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +495 -0
- data/lib/selenium/webdriver/bidi/protocol/browser.rb +250 -0
- data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +798 -0
- data/lib/selenium/webdriver/bidi/{log/base_log_entry.rb → protocol/domain.rb} +22 -11
- data/lib/selenium/webdriver/bidi/protocol/emulation.rb +448 -0
- data/lib/selenium/webdriver/bidi/protocol/error_code.rb +66 -0
- data/lib/selenium/webdriver/bidi/protocol/input.rb +354 -0
- data/lib/selenium/webdriver/bidi/protocol/log.rb +115 -0
- data/lib/selenium/webdriver/bidi/protocol/network.rb +720 -0
- data/lib/selenium/webdriver/bidi/protocol/permissions.rb +75 -0
- data/lib/selenium/webdriver/bidi/protocol/script.rb +1052 -0
- data/lib/selenium/webdriver/bidi/protocol/session.rb +286 -0
- data/lib/selenium/webdriver/bidi/protocol/speculation.rb +58 -0
- data/lib/selenium/webdriver/bidi/protocol/storage.rb +183 -0
- data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +88 -0
- data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +132 -0
- data/lib/selenium/webdriver/bidi/protocol.rb +42 -0
- data/lib/selenium/webdriver/bidi/serialization/record.rb +402 -0
- data/lib/selenium/webdriver/bidi/serialization/union.rb +156 -0
- data/lib/selenium/webdriver/bidi/serialization.rb +90 -0
- data/lib/selenium/webdriver/bidi/session.rb +4 -0
- data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +1420 -0
- data/lib/selenium/webdriver/bidi/support/check_generated.rb +63 -0
- data/lib/selenium/webdriver/bidi/transport.rb +57 -0
- data/lib/selenium/webdriver/bidi.rb +4 -1
- data/lib/selenium/webdriver/chrome/driver.rb +4 -3
- data/lib/selenium/webdriver/chrome/service.rb +13 -0
- data/lib/selenium/webdriver/chromium/driver.rb +0 -1
- data/lib/selenium/webdriver/chromium/options.rb +23 -1
- data/lib/selenium/webdriver/chromium/profile.rb +6 -0
- data/lib/selenium/webdriver/common/child_process.rb +2 -1
- data/lib/selenium/webdriver/common/client_config.rb +97 -0
- data/lib/selenium/webdriver/common/driver.rb +13 -7
- 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_extensions/{has_bidi.rb → has_session_events.rb} +19 -7
- data/lib/selenium/webdriver/common/driver_finder.rb +35 -25
- data/lib/selenium/webdriver/common/error.rb +10 -3
- data/lib/selenium/webdriver/common/local_driver.rb +29 -12
- data/lib/selenium/webdriver/common/logger.rb +28 -0
- data/lib/selenium/webdriver/common/manager.rb +2 -0
- data/lib/selenium/webdriver/common/options.rb +41 -2
- data/lib/selenium/webdriver/common/platform.rb +1 -3
- data/lib/selenium/webdriver/common/proxy.rb +1 -9
- data/lib/selenium/webdriver/common/selenium_manager.rb +2 -1
- data/lib/selenium/webdriver/common/service.rb +4 -8
- data/lib/selenium/webdriver/common/service_manager.rb +36 -4
- data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
- data/lib/selenium/webdriver/common/takes_screenshot.rb +1 -1
- data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +1 -1
- data/lib/selenium/webdriver/common/wait.rb +4 -1
- data/lib/selenium/webdriver/common/websocket_connection.rb +73 -37
- data/lib/selenium/webdriver/common.rb +2 -1
- data/lib/selenium/webdriver/devtools/response.rb +3 -3
- data/lib/selenium/webdriver/devtools.rb +1 -1
- data/lib/selenium/webdriver/edge/driver.rb +4 -3
- data/lib/selenium/webdriver/edge/service.rb +14 -0
- data/lib/selenium/webdriver/firefox/driver.rb +4 -6
- data/lib/selenium/webdriver/firefox/options.rb +19 -0
- data/lib/selenium/webdriver/firefox/profile.rb +13 -6
- data/lib/selenium/webdriver/firefox/service.rb +31 -2
- data/lib/selenium/webdriver/ie/driver.rb +4 -3
- data/lib/selenium/webdriver/ie/service.rb +10 -0
- data/lib/selenium/webdriver/remote/bidi_bridge.rb +44 -10
- data/lib/selenium/webdriver/remote/bridge.rb +20 -14
- data/lib/selenium/webdriver/remote/driver.rb +15 -4
- data/lib/selenium/webdriver/remote/features.rb +26 -1
- data/lib/selenium/webdriver/remote/http/common.rb +57 -12
- data/lib/selenium/webdriver/remote/http/curb.rb +6 -8
- data/lib/selenium/webdriver/remote/http/default.rb +56 -39
- data/lib/selenium/webdriver/safari/driver.rb +4 -3
- data/lib/selenium/webdriver/safari/options.rb +23 -4
- data/lib/selenium/webdriver/safari.rb +1 -6
- data/lib/selenium/webdriver/support/block_event_listener.rb +5 -1
- data/lib/selenium/webdriver/support/color.rb +14 -14
- data/lib/selenium/webdriver/support/event_firing_bridge.rb +5 -1
- data/lib/selenium/webdriver/support/guards/guard.rb +30 -14
- data/lib/selenium/webdriver/support/guards.rb +11 -4
- data/lib/selenium/webdriver/version.rb +1 -1
- data/lib/selenium/webdriver.rb +7 -2
- data/selenium-webdriver.gemspec +1 -1
- metadata +33 -11
- data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +0 -35
- data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +0 -33
- data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +0 -33
- data/lib/selenium/webdriver/bidi/log_inspector.rb +0 -147
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
# DO NOT EDIT! This file is generated by rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb.
|
|
21
|
+
# Regenerate with: `bazel run //rb/lib/selenium/webdriver:bidi-generate`
|
|
22
|
+
|
|
23
|
+
module Selenium
|
|
24
|
+
module WebDriver
|
|
25
|
+
class BiDi
|
|
26
|
+
module Protocol
|
|
27
|
+
# @api private
|
|
28
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
29
|
+
# @see https://w3c.github.io/webdriver-bidi/#module-webExtension
|
|
30
|
+
class WebExtension < Domain
|
|
31
|
+
# @api private
|
|
32
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
33
|
+
# @see https://w3c.github.io/webdriver-bidi/#cddl-type-webextensionextensiondata
|
|
34
|
+
class ExtensionData < Serialization::Union
|
|
35
|
+
discriminator 'type', {archive_path: 'archivePath', base64: 'base64', path: 'path'}
|
|
36
|
+
variants(
|
|
37
|
+
archive_path: 'WebExtension::ExtensionArchivePath',
|
|
38
|
+
base64: 'WebExtension::ExtensionBase64Encoded',
|
|
39
|
+
path: 'WebExtension::ExtensionPath'
|
|
40
|
+
)
|
|
41
|
+
object_only
|
|
42
|
+
|
|
43
|
+
def self.archive_path(**) = WebExtension::ExtensionArchivePath.new(**)
|
|
44
|
+
def self.base64(**) = WebExtension::ExtensionBase64Encoded.new(**)
|
|
45
|
+
def self.path(**) = WebExtension::ExtensionPath.new(**)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @api private
|
|
49
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
50
|
+
# @see https://w3c.github.io/webdriver-bidi/#cddl-type-webextensionextensionpath
|
|
51
|
+
ExtensionPath = Serialization::Record.define(
|
|
52
|
+
type: {fixed: 'path'},
|
|
53
|
+
path: {wire_key: 'path', primitive: 'string'}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# @api private
|
|
57
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
58
|
+
# @see https://w3c.github.io/webdriver-bidi/#cddl-type-webextensionextensionarchivepath
|
|
59
|
+
ExtensionArchivePath = Serialization::Record.define(
|
|
60
|
+
type: {fixed: 'archivePath'},
|
|
61
|
+
path: {wire_key: 'path', primitive: 'string'}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# @api private
|
|
65
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
66
|
+
# @see https://w3c.github.io/webdriver-bidi/#cddl-type-webextensionextensionbase64encoded
|
|
67
|
+
ExtensionBase64Encoded = Serialization::Record.define(
|
|
68
|
+
type: {fixed: 'base64'},
|
|
69
|
+
value: {wire_key: 'value', primitive: 'string'}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# @api private
|
|
73
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
74
|
+
# @see https://w3c.github.io/webdriver-bidi/#cddl-type-webextensioninstallresult
|
|
75
|
+
InstallResult = Serialization::Record.define(extension: {wire_key: 'extension', primitive: 'string'})
|
|
76
|
+
|
|
77
|
+
# @api private
|
|
78
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
79
|
+
# @see https://w3c.github.io/webdriver-bidi/#cddl-type-webextensionuninstallparameters
|
|
80
|
+
UninstallParameters = Serialization::Record.define(extension: {wire_key: 'extension', primitive: 'string'})
|
|
81
|
+
|
|
82
|
+
# @api private
|
|
83
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
84
|
+
# @see https://w3c.github.io/webdriver-bidi/#cddl-type-webextensioninstallparameters
|
|
85
|
+
InstallParameters = Serialization::Record.define(
|
|
86
|
+
extension_data: {wire_key: 'extensionData', ref: 'WebExtension::ExtensionData'},
|
|
87
|
+
extensible: true
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def extension_data = ExtensionData
|
|
91
|
+
def extension_path(**) = ExtensionPath.new(**)
|
|
92
|
+
def extension_archive_path(**) = ExtensionArchivePath.new(**)
|
|
93
|
+
def extension_base64_encoded(**) = ExtensionBase64Encoded.new(**)
|
|
94
|
+
def moz = Moz.new(connection)
|
|
95
|
+
|
|
96
|
+
# @api private
|
|
97
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
98
|
+
# @see https://w3c.github.io/webdriver-bidi/#command-webExtension-install
|
|
99
|
+
def install(extension_data:)
|
|
100
|
+
params = InstallParameters.new(extension_data: extension_data)
|
|
101
|
+
execute(cmd: 'webExtension.install', params: params, result: WebExtension::InstallResult)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @api private
|
|
105
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
106
|
+
# @see https://w3c.github.io/webdriver-bidi/#command-webExtension-uninstall
|
|
107
|
+
def uninstall(extension:)
|
|
108
|
+
params = UninstallParameters.new(extension: extension)
|
|
109
|
+
execute(cmd: 'webExtension.uninstall', params: params)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @api private
|
|
113
|
+
# moz: vendor variant of WebExtension, overriding commands with browser-specific params.
|
|
114
|
+
# Construct Moz.new(source) for a matching session; other sessions use WebExtension.
|
|
115
|
+
class Moz < WebExtension
|
|
116
|
+
# @api private
|
|
117
|
+
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
|
|
118
|
+
# @see https://w3c.github.io/webdriver-bidi/#command-webExtension-install
|
|
119
|
+
def install(extension_data:, allow_private_browsing: Serialization::UNSET, permanent: Serialization::UNSET)
|
|
120
|
+
extensions = {
|
|
121
|
+
'moz:allowPrivateBrowsing' => allow_private_browsing,
|
|
122
|
+
'moz:permanent' => permanent
|
|
123
|
+
}.reject { |_, value| Serialization::UNSET.equal?(value) }
|
|
124
|
+
params = InstallParameters.new(extension_data: extension_data, extensions: extensions)
|
|
125
|
+
execute(cmd: 'webExtension.install', params: params, result: WebExtension::InstallResult)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end # WebExtension
|
|
129
|
+
end # Protocol
|
|
130
|
+
end # BiDi
|
|
131
|
+
end # WebDriver
|
|
132
|
+
end # Selenium
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
# serialization must load first (it defines the Serialization runtime the generated
|
|
21
|
+
# classes build on), then the Domain base the generated classes subclass. Add a require
|
|
22
|
+
# below when a new BiDi domain is generated.
|
|
23
|
+
require 'selenium/webdriver/common/error'
|
|
24
|
+
require 'selenium/webdriver/bidi/serialization'
|
|
25
|
+
require 'selenium/webdriver/bidi/transport'
|
|
26
|
+
require 'selenium/webdriver/bidi/protocol/domain'
|
|
27
|
+
require 'selenium/webdriver/bidi/protocol/error_code'
|
|
28
|
+
require 'selenium/webdriver/bidi/error'
|
|
29
|
+
require 'selenium/webdriver/bidi/protocol/bluetooth'
|
|
30
|
+
require 'selenium/webdriver/bidi/protocol/browser'
|
|
31
|
+
require 'selenium/webdriver/bidi/protocol/browsing_context'
|
|
32
|
+
require 'selenium/webdriver/bidi/protocol/emulation'
|
|
33
|
+
require 'selenium/webdriver/bidi/protocol/input'
|
|
34
|
+
require 'selenium/webdriver/bidi/protocol/log'
|
|
35
|
+
require 'selenium/webdriver/bidi/protocol/network'
|
|
36
|
+
require 'selenium/webdriver/bidi/protocol/permissions'
|
|
37
|
+
require 'selenium/webdriver/bidi/protocol/script'
|
|
38
|
+
require 'selenium/webdriver/bidi/protocol/session'
|
|
39
|
+
require 'selenium/webdriver/bidi/protocol/speculation'
|
|
40
|
+
require 'selenium/webdriver/bidi/protocol/storage'
|
|
41
|
+
require 'selenium/webdriver/bidi/protocol/user_agent_client_hints'
|
|
42
|
+
require 'selenium/webdriver/bidi/protocol/web_extension'
|
|
@@ -0,0 +1,402 @@
|
|
|
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
|
+
:scalar, :const)
|
|
34
|
+
|
|
35
|
+
def self.define(**spec)
|
|
36
|
+
extensible = spec.delete(:extensible) || false
|
|
37
|
+
fields = spec.map { |name, meta| field(name, meta) }
|
|
38
|
+
names = fields.map(&:name)
|
|
39
|
+
names << :extensions if extensible
|
|
40
|
+
|
|
41
|
+
klass = super(*names)
|
|
42
|
+
fields.freeze
|
|
43
|
+
# Singleton methods are inherited by `X = Record.define(…)`; ivars would not.
|
|
44
|
+
klass.define_singleton_method(:fields) { fields }
|
|
45
|
+
klass.define_singleton_method(:extensible?) { extensible }
|
|
46
|
+
klass.include(Serializable)
|
|
47
|
+
# Capture ::Data's generated +new+, then prepend (not include) Deserializer so
|
|
48
|
+
# its +new+ overrides it — outbound +new+ adds validation, while inbound
|
|
49
|
+
# +from_json+ builds directly via the captured constructor. Bound to +self+ so a
|
|
50
|
+
# subclass builds itself, not the base.
|
|
51
|
+
data_new = klass.singleton_class.instance_method(:new)
|
|
52
|
+
klass.singleton_class.prepend(Deserializer)
|
|
53
|
+
klass.define_singleton_method(:construct) { |**attributes| data_new.bind_call(self, **attributes) }
|
|
54
|
+
klass.singleton_class.send(:private, :construct)
|
|
55
|
+
klass
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.field(name, meta)
|
|
59
|
+
meta = {wire_key: meta} if meta.is_a?(::String)
|
|
60
|
+
Field.new(name: name.to_sym, wire_key: meta.fetch(:wire_key, name.to_s),
|
|
61
|
+
nullable: meta[:nullable] || false, ref: meta[:ref],
|
|
62
|
+
list: meta[:list] || false, fixed: meta.fetch(:fixed, UNSET), enum: meta[:enum],
|
|
63
|
+
required: meta.fetch(:required, true), primitive: meta[:primitive],
|
|
64
|
+
scalar: meta[:scalar], const: meta.fetch(:const, UNSET))
|
|
65
|
+
end
|
|
66
|
+
private_class_method :field
|
|
67
|
+
|
|
68
|
+
# Inbound construction: the keyword +new+ (validated) and the wire +from_json+.
|
|
69
|
+
#
|
|
70
|
+
# @api private
|
|
71
|
+
module Deserializer
|
|
72
|
+
def new(**kwargs)
|
|
73
|
+
# Start from what was passed so ::Data's constructor rejects an unknown key, then fill
|
|
74
|
+
# each field with its value or UNSET (omitted), forcing fixed discriminators.
|
|
75
|
+
attributes = kwargs.dup
|
|
76
|
+
fields.each { |f| attributes[f.name] = fixed?(f) ? f.fixed : attributes.fetch(f.name, UNSET) }
|
|
77
|
+
attributes[:extensions] = kwargs.fetch(:extensions, {}) if extensible?
|
|
78
|
+
validate_values(attributes)
|
|
79
|
+
construct(**attributes)
|
|
80
|
+
end
|
|
81
|
+
|
|
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.
|
|
86
|
+
def from_json(json_payload)
|
|
87
|
+
unless json_payload.is_a?(::Hash)
|
|
88
|
+
raise Error::WebDriverError, "#{name} expected an object on the wire, got #{json_payload.inspect}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
attributes = fields.to_h do |f|
|
|
92
|
+
[f.name, wire_value(f, json_payload)]
|
|
93
|
+
end
|
|
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
|
|
100
|
+
construct(**attributes)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
# Checks each field's value: a required field cannot be omitted (UNSET), a non-nullable
|
|
106
|
+
# field cannot be nil (nil is neither a value nor the UNSET omit-sentinel, so it would be
|
|
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+.
|
|
112
|
+
def validate_values(attributes)
|
|
113
|
+
fields.each do |f|
|
|
114
|
+
value = attributes[f.name]
|
|
115
|
+
raise ::ArgumentError, "#{name}##{f.name} is required" if UNSET.equal?(value) && f.required
|
|
116
|
+
raise ::ArgumentError, "#{name}##{f.name} cannot be nil" if value.nil? && !f.nullable
|
|
117
|
+
next if value.nil? || UNSET.equal?(value)
|
|
118
|
+
|
|
119
|
+
validate_present(f, value)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
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
|
+
|
|
192
|
+
# Outbound mirror of check_shape: a list-typed arg must be an array, a scalar-shaped one
|
|
193
|
+
# (enum or ref, not a list) must not — a local ArgumentError, not a wire round-trip.
|
|
194
|
+
def check_outbound_shape(field, value)
|
|
195
|
+
return if field.list == value.is_a?(::Array)
|
|
196
|
+
return unless field.list || field.enum || field.ref
|
|
197
|
+
|
|
198
|
+
kind = field.list ? 'a list' : 'a single value'
|
|
199
|
+
raise ::ArgumentError, "#{name}##{field.name} expected #{kind}, got #{value.inspect}"
|
|
200
|
+
end
|
|
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
|
+
|
|
213
|
+
def fixed?(field)
|
|
214
|
+
!UNSET.equal?(field.fixed)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def wire_value(field, json_payload)
|
|
218
|
+
return field.fixed if fixed?(field)
|
|
219
|
+
return read(field, json_payload[field.wire_key]) if json_payload.key?(field.wire_key)
|
|
220
|
+
return UNSET unless field.required
|
|
221
|
+
|
|
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
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def read(field, raw)
|
|
238
|
+
if raw.nil?
|
|
239
|
+
return raw if field.nullable
|
|
240
|
+
|
|
241
|
+
raise Error::WebDriverError, "#{name}##{field.name} received null but is not nullable"
|
|
242
|
+
end
|
|
243
|
+
check_shape(field, raw)
|
|
244
|
+
return Serialization.to_symbol("#{name}##{field.name}", raw, enum_hash(field)) if field.enum
|
|
245
|
+
|
|
246
|
+
if field.ref.nil?
|
|
247
|
+
check_primitive(field, raw) unless field.list
|
|
248
|
+
return raw
|
|
249
|
+
end
|
|
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)
|
|
259
|
+
klass = (@refs ||= {})[field.name] ||= Protocol.const_get(field.ref)
|
|
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)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# A declared list must arrive as an array; a scalar-shaped field (enum or ref, not a
|
|
266
|
+
# list) must not. An opaque field carries no shape descriptor, so it passes through.
|
|
267
|
+
def check_shape(field, raw)
|
|
268
|
+
return if field.list == raw.is_a?(::Array)
|
|
269
|
+
return unless field.list || field.enum || field.ref
|
|
270
|
+
|
|
271
|
+
raise Error::WebDriverError,
|
|
272
|
+
"#{name}##{field.name} expected #{field.list ? 'a list' : 'a single value'}, got #{raw.inspect}"
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Ruby classes a checkable primitive admits. `number` is any Numeric (JSON has one
|
|
276
|
+
# number type); `integer` requires an Integer — a browser emits `5`, not `5.0`, for an
|
|
277
|
+
# integer (JS has no int/float split), so this rarely false-positives yet still rejects
|
|
278
|
+
# a genuine non-integer like 1.5. A field with no primitive descriptor is left unchecked.
|
|
279
|
+
PRIMITIVE_TYPES = {
|
|
280
|
+
'string' => [::String], 'boolean' => [::TrueClass, ::FalseClass],
|
|
281
|
+
'number' => [::Numeric], 'integer' => [::Integer]
|
|
282
|
+
}.freeze
|
|
283
|
+
|
|
284
|
+
def check_primitive(field, raw)
|
|
285
|
+
expected = PRIMITIVE_TYPES[field.primitive]
|
|
286
|
+
return if expected.nil? || expected.any? { |type| raw.is_a?(type) }
|
|
287
|
+
|
|
288
|
+
raise Error::WebDriverError, "#{name}##{field.name} expected #{field.primitive}, got #{raw.inspect}"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def enum_hash(field)
|
|
292
|
+
(@enums ||= {})[field.name] ||= Protocol.const_get(field.enum)
|
|
293
|
+
end
|
|
294
|
+
|
|
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}"
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def extra(json_payload)
|
|
340
|
+
known = (@wire_keys ||= fields.map(&:wire_key))
|
|
341
|
+
json_payload.except(*known)
|
|
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
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# @api private
|
|
356
|
+
module Serializable
|
|
357
|
+
def self.as_json(value)
|
|
358
|
+
case value
|
|
359
|
+
when Serializable then value.as_json
|
|
360
|
+
when ::Array then value.map { |element| as_json(element) }
|
|
361
|
+
when ::Hash then value.transform_values { |element| as_json(element) }
|
|
362
|
+
else value
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Omit UNSET fields; emit null only for nullable ones.
|
|
367
|
+
def as_json(*)
|
|
368
|
+
payload = {}
|
|
369
|
+
self.class.fields.each do |f|
|
|
370
|
+
value = public_send(f.name)
|
|
371
|
+
next if UNSET.equal?(value)
|
|
372
|
+
next if value.nil? && !f.nullable
|
|
373
|
+
|
|
374
|
+
value = Serialization.to_wire(value, Protocol.const_get(f.enum)) if f.enum
|
|
375
|
+
payload[f.wire_key] = Serializable.as_json(value)
|
|
376
|
+
end
|
|
377
|
+
merge_extensions!(payload) if self.class.extensible? && !extensions.empty?
|
|
378
|
+
payload
|
|
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
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end # BiDi
|
|
401
|
+
end # WebDriver
|
|
402
|
+
end # Selenium
|