appium_lib_core 4.5.0 → 5.0.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/unittest.yml +2 -2
- data/CHANGELOG.md +12 -5
- data/README.md +2 -1
- data/appium_lib_core.gemspec +4 -4
- data/lib/appium_lib_core.rb +2 -5
- data/lib/appium_lib_core/android/device/auth_finger_print.rb +2 -1
- data/lib/appium_lib_core/common/base.rb +0 -3
- data/lib/appium_lib_core/common/base/bridge.rb +284 -89
- data/lib/appium_lib_core/common/base/capabilities.rb +3 -3
- data/lib/appium_lib_core/common/base/{command.rb → device_ime.rb} +32 -7
- data/lib/appium_lib_core/common/base/driver.rb +128 -70
- data/lib/appium_lib_core/common/base/driver_settings.rb +51 -0
- data/lib/appium_lib_core/common/base/has_network_connection.rb +56 -0
- data/lib/appium_lib_core/common/base/rotable.rb +1 -1
- data/lib/appium_lib_core/common/base/search_context.rb +9 -4
- data/lib/appium_lib_core/common/command.rb +259 -4
- data/lib/appium_lib_core/common/device/keyevent.rb +4 -4
- data/lib/appium_lib_core/common/error.rb +4 -1
- data/lib/appium_lib_core/common/log.rb +4 -1
- data/lib/appium_lib_core/device.rb +1 -5
- data/lib/appium_lib_core/driver.rb +3 -4
- data/lib/appium_lib_core/{patch.rb → element.rb} +2 -7
- data/lib/appium_lib_core/ios/uiautomation/patch.rb +1 -1
- data/lib/appium_lib_core/version.rb +2 -2
- data/release_notes.md +14 -0
- data/script/commands.rb +3 -37
- metadata +21 -30
- data/lib/appium_lib_core/common/base/bridge/mjsonwp.rb +0 -91
- data/lib/appium_lib_core/common/base/bridge/w3c.rb +0 -248
- data/lib/appium_lib_core/common/command/common.rb +0 -110
- data/lib/appium_lib_core/common/command/mjsonwp.rb +0 -28
- data/lib/appium_lib_core/common/command/w3c.rb +0 -56
@@ -19,10 +19,10 @@ module Appium
|
|
19
19
|
# @private
|
20
20
|
# @param [Hash] opts_caps Capabilities for Appium server. All capability keys are converted to lowerCamelCase when
|
21
21
|
# this client sends capabilities to Appium server as JSON format.
|
22
|
-
# @return [::Selenium::WebDriver::Remote::
|
23
|
-
# inherited ::Selenium::WebDriver::Remote::
|
22
|
+
# @return [::Selenium::WebDriver::Remote::Capabilities] Return instance of Appium::Core::Base::Capabilities
|
23
|
+
# inherited ::Selenium::WebDriver::Remote::Capabilities
|
24
24
|
def self.create_capabilities(opts_caps = {})
|
25
|
-
::Selenium::WebDriver::Remote::
|
25
|
+
::Selenium::WebDriver::Remote::Capabilities.new(opts_caps)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -15,10 +15,35 @@
|
|
15
15
|
module Appium
|
16
16
|
module Core
|
17
17
|
class Base
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
class DeviceIME
|
22
|
+
# @private this class is private
|
23
|
+
def initialize(bridge)
|
24
|
+
@bridge = bridge
|
25
|
+
end
|
26
|
+
|
27
|
+
def activate(ime_name)
|
28
|
+
@bridge.ime_activate(ime_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def available_engines
|
32
|
+
@bridge.ime_available_engines
|
33
|
+
end
|
34
|
+
|
35
|
+
def active_engine
|
36
|
+
@bridge.ime_active_engine
|
37
|
+
end
|
38
|
+
|
39
|
+
def activated?
|
40
|
+
@bridge.ime_activated
|
41
|
+
end
|
42
|
+
|
43
|
+
def deactivate
|
44
|
+
@bridge.ime_deactivate
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -13,11 +13,14 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
15
|
require 'base64'
|
16
|
+
require_relative 'device_ime'
|
17
|
+
require_relative 'driver_settings'
|
16
18
|
require_relative 'search_context'
|
17
19
|
require_relative 'screenshot'
|
18
20
|
require_relative 'rotable'
|
19
21
|
require_relative 'remote_status'
|
20
22
|
require_relative 'has_location'
|
23
|
+
require_relative 'has_network_connection'
|
21
24
|
|
22
25
|
module Appium
|
23
26
|
module Core
|
@@ -27,31 +30,63 @@ module Appium
|
|
27
30
|
include ::Selenium::WebDriver::DriverExtensions::HasSessionId
|
28
31
|
include ::Selenium::WebDriver::DriverExtensions::HasRemoteStatus
|
29
32
|
include ::Selenium::WebDriver::DriverExtensions::HasWebStorage
|
30
|
-
include ::Selenium::WebDriver::DriverExtensions::HasNetworkConnection
|
31
|
-
include ::Selenium::WebDriver::DriverExtensions::HasTouchScreen
|
32
33
|
|
33
34
|
include ::Appium::Core::Base::Rotatable
|
34
35
|
include ::Appium::Core::Base::SearchContext
|
35
36
|
include ::Appium::Core::Base::TakesScreenshot
|
36
37
|
include ::Appium::Core::Base::HasRemoteStatus
|
37
38
|
include ::Appium::Core::Base::HasLocation
|
39
|
+
include ::Appium::Core::Base::HasNetworkConnection
|
40
|
+
|
41
|
+
private
|
38
42
|
|
39
43
|
# Private API.
|
40
44
|
# Do not use this for general use. Used by flutter driver to get bridge for creating a new element
|
41
45
|
attr_reader :bridge
|
42
46
|
|
43
|
-
def initialize(
|
44
|
-
|
45
|
-
@bridge = ::Appium::Core::Base::Bridge.handshake(**opts)
|
46
|
-
super(@bridge, listener: listener)
|
47
|
+
def initialize(bridge: nil, listener: nil, **opts)
|
48
|
+
super
|
47
49
|
end
|
48
50
|
|
49
|
-
#
|
50
|
-
#
|
51
|
-
|
52
|
-
|
51
|
+
# Implements protocol handshake which:
|
52
|
+
#
|
53
|
+
# 1. Creates session with driver.
|
54
|
+
# 2. Sniffs response.
|
55
|
+
# 3. Based on the response, understands which dialect we should use.
|
56
|
+
#
|
57
|
+
# @return [::Appium::Core::Base::Bridge]
|
58
|
+
#
|
59
|
+
def create_bridge(**opts)
|
60
|
+
# TODO: probably Appium does not need this.
|
61
|
+
opts[:url] ||= service_url(opts)
|
62
|
+
caps = opts.delete(:capabilities)
|
63
|
+
# NOTE: This is deprecated
|
64
|
+
cap_array = caps.is_a?(Hash) ? [caps] : Array(caps)
|
65
|
+
|
66
|
+
desired_capabilities = opts.delete(:desired_capabilities)
|
67
|
+
if desired_capabilities
|
68
|
+
if desired_capabilities.is_a?(Hash)
|
69
|
+
desired_capabilities = ::Selenium::WebDriver::Remote::Capabilities(desired_capabilities)
|
70
|
+
end
|
71
|
+
cap_array << desired_capabilities
|
72
|
+
end
|
73
|
+
|
74
|
+
options = opts.delete(:options)
|
75
|
+
cap_array << options if options
|
76
|
+
|
77
|
+
capabilities = generate_capabilities(cap_array)
|
78
|
+
|
79
|
+
bridge_opts = { http_client: opts.delete(:http_client), url: opts.delete(:url) }
|
80
|
+
raise ::Appium::Core::Error::ArgumentError, "Unable to create a driver with parameters: #{opts}" unless opts.empty?
|
81
|
+
|
82
|
+
bridge = ::Appium::Core::Base::Bridge.new(**bridge_opts)
|
83
|
+
|
84
|
+
bridge.create_session(capabilities)
|
85
|
+
bridge
|
53
86
|
end
|
54
87
|
|
88
|
+
public
|
89
|
+
|
55
90
|
# Update +server_url+ and HTTP clients following this arguments, protocol, host, port and path.
|
56
91
|
# After this method, +@bridge.http+ will be a new instance following them instead of +server_url+ which is
|
57
92
|
# set before creating session.
|
@@ -76,6 +111,75 @@ module Appium
|
|
76
111
|
path: path)
|
77
112
|
end
|
78
113
|
|
114
|
+
AVAILABLE_METHODS = [
|
115
|
+
:get, :head, :post, :put, :delete,
|
116
|
+
:connect, :options, :trace, :patch
|
117
|
+
].freeze
|
118
|
+
# Define a new custom method to the driver so that you can define your own method for
|
119
|
+
# drivers/plugins in Appium 2.0. Appium 2.0 and its custom drivers/plugins allow you
|
120
|
+
# to define custom commands that are not part of W3C spec.
|
121
|
+
#
|
122
|
+
# @param [Symbol] method HTTP request method as https://www.w3.org/TR/webdriver/#endpoints
|
123
|
+
# @param [string] url The url to URL template as https://www.w3.org/TR/webdriver/#endpoints.
|
124
|
+
# +:session_id+ is the placeholder of 'session id'.
|
125
|
+
# Other place holders can be specified with +:+ prefix like +:id+.
|
126
|
+
# Then, the +:id+ will be replaced with a given value as the seconds argument of +execute+
|
127
|
+
# @param [Symbol] name The name of method that is called as the driver instance method.
|
128
|
+
# @param [Proc] block The block to involve as the method
|
129
|
+
# @raise [ArgumentError] If the given +name+ is already defined or +method+ are invalid value.
|
130
|
+
#
|
131
|
+
# @example
|
132
|
+
#
|
133
|
+
# @driver.add_command(
|
134
|
+
# method: :get,
|
135
|
+
# url: 'session/:session_id/path/to/custom/url',
|
136
|
+
# name: :test_command
|
137
|
+
# )
|
138
|
+
# # Send a GET request to 'session/<session id>/path/to/custom/url'
|
139
|
+
# @driver.test_command
|
140
|
+
#
|
141
|
+
#
|
142
|
+
# @driver.add_command(
|
143
|
+
# method: :post,
|
144
|
+
# url: 'session/:session_id/path/to/custom/url',
|
145
|
+
# name: :test_command
|
146
|
+
# ) do
|
147
|
+
# def test_command(argument)
|
148
|
+
# execute(:test_command, {}, { dummy: argument })
|
149
|
+
# end
|
150
|
+
# end
|
151
|
+
# # Send a POST request to 'session/<session id>/path/to/custom/url'
|
152
|
+
# # with body "{ dummy: 1 }" as JSON object. "1" is the argument.
|
153
|
+
# # ':session_id' in the given 'url' is replaced with current 'session id'.
|
154
|
+
# @driver.test_command(1)
|
155
|
+
#
|
156
|
+
# @driver.add_command(
|
157
|
+
# method: :post,
|
158
|
+
# url: 'session/:session_id/element/:id/custom/action',
|
159
|
+
# name: :test_action_command
|
160
|
+
# ) do
|
161
|
+
# def test_action_command(element_id, action)
|
162
|
+
# execute(:test_action_command, {id: element_id}, { dummy_action: action })
|
163
|
+
# end
|
164
|
+
# end
|
165
|
+
# # Send a POST request to 'session/<session id>/element/<element id>/custom/action'
|
166
|
+
# # with body "{ dummy_action: #{action} }" as JSON object. "action" is the seconds argument.
|
167
|
+
# # ':session_id' in the given url is replaced with current 'session id'.
|
168
|
+
# # ':id' in the given url is replaced with the given 'element_id'.
|
169
|
+
# e = @driver.find_element :accessibility_id, 'an element'
|
170
|
+
# @driver.test_action_command(e.ref, 'action')
|
171
|
+
#
|
172
|
+
def add_command(method:, url:, name:, &block)
|
173
|
+
unless AVAILABLE_METHODS.include? method
|
174
|
+
raise ::Appium::Core::Error::ArgumentError, "Available method is either #{AVAILABLE_METHODS}"
|
175
|
+
end
|
176
|
+
|
177
|
+
# TODO: Remove this logger before Appium 2.0 release
|
178
|
+
::Appium::Logger.info '[Experimental] this method is experimental for Appium 2.0. This interface may change.'
|
179
|
+
|
180
|
+
@bridge.add_command method: method, url: url, name: name, &block
|
181
|
+
end
|
182
|
+
|
79
183
|
### Methods for Appium
|
80
184
|
|
81
185
|
# Lock the device
|
@@ -158,22 +262,6 @@ module Appium
|
|
158
262
|
end
|
159
263
|
alias type send_keys
|
160
264
|
|
161
|
-
class DriverSettings
|
162
|
-
# @private this class is private
|
163
|
-
def initialize(bridge)
|
164
|
-
@bridge = bridge
|
165
|
-
end
|
166
|
-
|
167
|
-
def get
|
168
|
-
@bridge.get_settings
|
169
|
-
end
|
170
|
-
|
171
|
-
def update(settings)
|
172
|
-
@bridge.update_settings(settings)
|
173
|
-
end
|
174
|
-
end
|
175
|
-
private_constant :DriverSettings
|
176
|
-
|
177
265
|
# Returns an instance of DriverSettings to call get/update.
|
178
266
|
#
|
179
267
|
# @example
|
@@ -182,7 +270,7 @@ module Appium
|
|
182
270
|
# @driver.settings.update('allowInvisibleElements': true)
|
183
271
|
#
|
184
272
|
def settings
|
185
|
-
@
|
273
|
+
@settings ||= DriverSettings.new(@bridge)
|
186
274
|
end
|
187
275
|
|
188
276
|
# Get appium Settings for current test session.
|
@@ -204,8 +292,8 @@ module Appium
|
|
204
292
|
#
|
205
293
|
# @example
|
206
294
|
#
|
207
|
-
# @driver.update_settings('allowInvisibleElements': true)
|
208
|
-
# @driver.settings.update('allowInvisibleElements': true)
|
295
|
+
# @driver.update_settings({ 'allowInvisibleElements': true })
|
296
|
+
# @driver.settings.update({ 'allowInvisibleElements': true })
|
209
297
|
# @driver.settings = { 'allowInvisibleElements': true }
|
210
298
|
#
|
211
299
|
def settings=(value)
|
@@ -213,34 +301,6 @@ module Appium
|
|
213
301
|
end
|
214
302
|
alias update_settings settings=
|
215
303
|
|
216
|
-
class DeviceIME
|
217
|
-
# @private this class is private
|
218
|
-
def initialize(bridge)
|
219
|
-
@bridge = bridge
|
220
|
-
end
|
221
|
-
|
222
|
-
def activate(ime_name)
|
223
|
-
@bridge.ime_activate(ime_name)
|
224
|
-
end
|
225
|
-
|
226
|
-
def available_engines
|
227
|
-
@bridge.ime_available_engines
|
228
|
-
end
|
229
|
-
|
230
|
-
def active_engine
|
231
|
-
@bridge.ime_active_engine
|
232
|
-
end
|
233
|
-
|
234
|
-
def activated?
|
235
|
-
@bridge.ime_activated
|
236
|
-
end
|
237
|
-
|
238
|
-
def deactivate
|
239
|
-
@bridge.ime_deactivate
|
240
|
-
end
|
241
|
-
end
|
242
|
-
private_constant :DeviceIME
|
243
|
-
|
244
304
|
# Returns an instance of DeviceIME
|
245
305
|
#
|
246
306
|
# @return [Appium::Core::Base::Driver::DeviceIME]
|
@@ -254,7 +314,7 @@ module Appium
|
|
254
314
|
# @driver.ime.deactivate #=> Deactivate current IME engine
|
255
315
|
#
|
256
316
|
def ime
|
257
|
-
@
|
317
|
+
@ime ||= DeviceIME.new(@bridge)
|
258
318
|
end
|
259
319
|
|
260
320
|
# Android only. Make an engine that is available active.
|
@@ -817,7 +877,7 @@ module Appium
|
|
817
877
|
# @driver.perform_actions [f1, f2] #=> 'nil' if the action succeed
|
818
878
|
#
|
819
879
|
def perform_actions(data)
|
820
|
-
raise ArgumentError, "'#{data}' must be Array" unless data.is_a? Array
|
880
|
+
raise ::Appium::Core::Error::ArgumentError, "'#{data}' must be Array" unless data.is_a? Array
|
821
881
|
|
822
882
|
@bridge.send_actions data.map(&:encode).compact
|
823
883
|
data.each(&:clear_actions)
|
@@ -887,16 +947,15 @@ module Appium
|
|
887
947
|
# Retrieve the capabilities of the specified session.
|
888
948
|
# It's almost same as +@driver.capabilities+ but you can get more details.
|
889
949
|
#
|
890
|
-
# @return [Selenium::WebDriver::Remote::Capabilities, Selenium::WebDriver::Remote::
|
950
|
+
# @return [Selenium::WebDriver::Remote::Capabilities, Selenium::WebDriver::Remote::Capabilities]
|
891
951
|
#
|
892
952
|
# @example
|
893
953
|
# @driver.session_capabilities
|
894
954
|
#
|
895
955
|
# #=> uiautomator2
|
896
|
-
# # <Selenium::WebDriver::Remote::
|
956
|
+
# # <Selenium::WebDriver::Remote::Capabilities:0x007fa38dae1360
|
897
957
|
# # @capabilities=
|
898
|
-
# # {:
|
899
|
-
# # :browser_name=>nil,
|
958
|
+
# # {:browser_name=>nil,
|
900
959
|
# # :browser_version=>nil,
|
901
960
|
# # :platform_name=>"android",
|
902
961
|
# # :page_load_strategy=>nil,
|
@@ -943,10 +1002,9 @@ module Appium
|
|
943
1002
|
# # "viewportRect"=>{"left"=>0, "top"=>63, "width"=>1080, "height"=>1731}}>
|
944
1003
|
# #
|
945
1004
|
# #=> XCUITest
|
946
|
-
# # <Selenium::WebDriver::Remote::
|
1005
|
+
# # <Selenium::WebDriver::Remote::Capabilities:0x007fb15dc01370
|
947
1006
|
# # @capabilities=
|
948
|
-
# # {:
|
949
|
-
# # :browser_name=>"UICatalog",
|
1007
|
+
# # {:browser_name=>"UICatalog",
|
950
1008
|
# # :browser_version=>nil,
|
951
1009
|
# # :platform_name=>"ios",
|
952
1010
|
# # :page_load_strategy=>nil,
|
@@ -1027,7 +1085,7 @@ module Appium
|
|
1027
1085
|
#
|
1028
1086
|
# @param [String] img_path A path to a partial image you'd like to find
|
1029
1087
|
#
|
1030
|
-
# @return [::
|
1088
|
+
# @return [::Appium::Core::Element]
|
1031
1089
|
#
|
1032
1090
|
# @example
|
1033
1091
|
#
|
@@ -1097,14 +1155,14 @@ module Appium
|
|
1097
1155
|
@bridge.execute_driver(script: script, type: type, timeout_ms: timeout_ms)
|
1098
1156
|
end
|
1099
1157
|
|
1100
|
-
# Convert vanilla element response to ::
|
1158
|
+
# Convert vanilla element response to ::Appium::Core::Element
|
1101
1159
|
#
|
1102
1160
|
# @param [Hash] id The id which can get as a response from server
|
1103
|
-
# @return [::
|
1161
|
+
# @return [::Appium::Core::Element]
|
1104
1162
|
#
|
1105
1163
|
# @example
|
1106
1164
|
# response = {"element-6066-11e4-a52e-4f735466cecf"=>"xxxx", "ELEMENT"=>"xxxx"}
|
1107
|
-
# ele = @driver.convert_to_element(response) #=> ::
|
1165
|
+
# ele = @driver.convert_to_element(response) #=> ::Appium::Core::Element
|
1108
1166
|
# ele.rect #=> Can get the rect of the element
|
1109
1167
|
#
|
1110
1168
|
def convert_to_element(id)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module Appium
|
16
|
+
module Core
|
17
|
+
class Base
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
class DriverSettings
|
22
|
+
# @private this class is private
|
23
|
+
def initialize(bridge)
|
24
|
+
@bridge = bridge
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get appium Settings for current test session.
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
#
|
31
|
+
# @driver.settings.get
|
32
|
+
#
|
33
|
+
def get
|
34
|
+
@bridge.get_settings
|
35
|
+
end
|
36
|
+
|
37
|
+
# Update Appium Settings for current test session
|
38
|
+
#
|
39
|
+
# @param [Hash] settings Settings to update, keys are settings, values to value to set each setting to
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
#
|
43
|
+
# @driver.settings.update({'allowInvisibleElements': true})
|
44
|
+
#
|
45
|
+
def update(settings)
|
46
|
+
@bridge.update_settings(settings)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module Appium
|
16
|
+
module Core
|
17
|
+
class Base
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
module HasNetworkConnection
|
22
|
+
def network_connection_type
|
23
|
+
connection_value = @bridge.network_connection
|
24
|
+
|
25
|
+
connection_type = values_to_type[connection_value]
|
26
|
+
|
27
|
+
# In case the connection type is not recognized return the
|
28
|
+
# connection value.
|
29
|
+
connection_type || connection_value
|
30
|
+
end
|
31
|
+
|
32
|
+
def network_connection_type=(connection_type)
|
33
|
+
raise ::Appium::Core::Error::ArgumentError, 'Invalid connection type' unless valid_type? connection_type
|
34
|
+
|
35
|
+
connection_value = type_to_values[connection_type]
|
36
|
+
|
37
|
+
@bridge.network_connection = connection_value
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def type_to_values
|
43
|
+
{ airplane_mode: 1, wifi: 2, data: 4, all: 6, none: 0 }
|
44
|
+
end
|
45
|
+
|
46
|
+
def values_to_type
|
47
|
+
type_to_values.invert
|
48
|
+
end
|
49
|
+
|
50
|
+
def valid_type?(type)
|
51
|
+
type_to_values.keys.include? type
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|