appium_lib_core 5.0.0.beta1 → 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -6
  3. data/README.md +2 -1
  4. data/Rakefile +4 -0
  5. data/appium_lib_core.gemspec +3 -3
  6. data/ci-jobs/functional_test.yml +1 -1
  7. data/docs/mobile_command.md +3 -2
  8. data/lib/appium_lib_core/android/device/auth_finger_print.rb +2 -1
  9. data/lib/appium_lib_core/common/base/bridge.rb +303 -7
  10. data/lib/appium_lib_core/common/base/device_ime.rb +49 -0
  11. data/lib/appium_lib_core/common/base/driver.rb +101 -105
  12. data/lib/appium_lib_core/common/base/driver_settings.rb +51 -0
  13. data/lib/appium_lib_core/common/base/has_location.rb +11 -4
  14. data/lib/appium_lib_core/common/base/has_network_connection.rb +1 -1
  15. data/lib/appium_lib_core/common/base/rotable.rb +1 -1
  16. data/lib/appium_lib_core/common/base/screenshot.rb +4 -3
  17. data/lib/appium_lib_core/common/base/search_context.rb +9 -4
  18. data/lib/appium_lib_core/common/base.rb +0 -2
  19. data/lib/appium_lib_core/common/command.rb +259 -3
  20. data/lib/appium_lib_core/common/device/keyevent.rb +4 -4
  21. data/lib/appium_lib_core/common/device/value.rb +4 -4
  22. data/lib/appium_lib_core/common/error.rb +4 -1
  23. data/lib/appium_lib_core/common/log.rb +4 -1
  24. data/lib/appium_lib_core/common/touch_action/touch_actions.rb +4 -1
  25. data/lib/appium_lib_core/device.rb +1 -1
  26. data/lib/appium_lib_core/driver.rb +3 -4
  27. data/lib/appium_lib_core/{patch.rb → element.rb} +9 -14
  28. data/lib/appium_lib_core/ios/uiautomation/patch.rb +1 -1
  29. data/lib/appium_lib_core/version.rb +2 -2
  30. data/lib/appium_lib_core.rb +2 -5
  31. data/release_notes.md +23 -0
  32. data/script/commands.rb +1 -12
  33. metadata +15 -17
  34. data/lib/appium_lib_core/common/base/bridge/w3c.rb +0 -348
  35. data/lib/appium_lib_core/common/base/command.rb +0 -145
  36. data/lib/appium_lib_core/common/command/common.rb +0 -110
  37. data/lib/appium_lib_core/common/command/w3c.rb +0 -56
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd462f64262abe99f03dd5a421f2a2dd9c8064f1d44eff47bc1872a834793d27
4
- data.tar.gz: 1a1ca492c3551d8c992e9f2b740460e67239887400602351423062e49912b73f
3
+ metadata.gz: fc513ad9f9ba5689303763b09a884af18e1a9eb80e78a6e05cf838551905f732
4
+ data.tar.gz: 59241d4d8626cf39eeb63a51291f18deca6759bdba95a8e2bb942173e0b5df9e
5
5
  SHA512:
6
- metadata.gz: d9e4977dfb93ce734c59af5053a6331bfc2038bd3a03757a31fd7e48c3cdcc810204fb6f3027a1120afe91eda31ad61aa504fa25fdf2a2a21d36638c27982f8e
7
- data.tar.gz: 3e23c561984a3ad4bf4214aad79fc730f8673d0bc29c81d26ff5143c0dbeb38d6eacc9278ba0caa11e4dfc51f82267f79b97efe6937ad8315c837e64876df162
6
+ metadata.gz: 867c6a7bfbdf95243fae3a6292f0349bf9bda1fbb83b44cb7d2e12e8f5e0ecb2f41fb6103aeebdcaaded1024cbb16cd2d758d73bef9bf00697325d75b5c2d027
7
+ data.tar.gz: 345771f473fca33668d101f00be374ebb3b345b6795c5392b9955da73bb45588e5589b70ba8c983ce161e35c311b2f18adfd7bac3a8d816ba93863820b05b4f6
data/CHANGELOG.md CHANGED
@@ -5,27 +5,34 @@ Read `release_notes.md` for commit level details.
5
5
  ## [Unreleased]
6
6
 
7
7
  ### Enhancements
8
+ - Allow to override an existing method by `Appium::Core::Base::Driver#add_command` since Appium drivers/plugins allow to override them
8
9
 
9
10
  ### Bug fixes
10
11
 
11
12
  ### Deprecations
12
13
 
13
- ## [5.0.0.beta1]
14
+ ## [5.0.0.beta]
14
15
 
15
16
  - Update base selenium webdriver version to `4.0.0.beta2`
16
17
  - Support only W3C spec
17
18
  - Support Ruby 2.5+
19
+ - Raises `::Appium::Core::Error::ArgumentError` instead of `ArgumentError` for this library specific argument errors
18
20
 
19
- ## [4.5.0] - 2021-03-14
21
+ ## [4.7.0] - 2021-07-17
20
22
 
21
23
  ### Enhancements
22
- - Add `speed` argument for `Appium::Core::Base::Driver#set_location` since Appium 1.21.0
23
- - Add `multiple` and `match_neighbour_threshold` arguments for `Appium::Core::Base::Driver#find_image_occurrence`
24
+ - Add `satellites` option in `Appium::Core::Base::Driver#set_location`
24
25
 
25
- ### Bug fixes
26
+ ## [4.6.0] - 2021-06-03
26
27
 
27
- ### Deprecations
28
+ ### Enhancements
29
+ - Add `Appium::Core::Base::Driver#add_command` to allow you to add your own command
28
30
 
31
+ ## [4.5.0] - 2021-03-14
32
+
33
+ ### Enhancements
34
+ - Add `speed` argument for `Appium::Core::Base::Driver#set_location` since Appium 1.21.0
35
+ - Add `multiple` and `match_neighbour_threshold` arguments for `Appium::Core::Base::Driver#find_image_occurrence`
29
36
 
30
37
  ## [4.4.1(4.4.0)] - 2021-02-15(2021-02-13)
31
38
 
data/README.md CHANGED
@@ -11,6 +11,7 @@ This library wraps [selenium-webdriver](https://github.com/SeleniumHQ/selenium/w
11
11
  # Documentation
12
12
 
13
13
  - http://www.rubydoc.info/github/appium/ruby_lib_core
14
+ - You can find working API examples in test code, [test/functional](test/functional)
14
15
 
15
16
  # Related library
16
17
  - https://github.com/appium/ruby_lib
@@ -120,7 +121,7 @@ $ IGNORE_VERSION_SKIP=true CI=true bundle exec rake test:func:android
120
121
  $ ruby test.rb
121
122
  ```
122
123
 
123
- More examples are in [test/functional](test/functional)
124
+ More examples are in [test/functional](test/functional)
124
125
 
125
126
  ### Capabilities
126
127
 
data/Rakefile CHANGED
@@ -38,6 +38,7 @@ namespace :test do
38
38
  namespace :unit do
39
39
  desc('Run all iOS related unit tests in test directory')
40
40
  Rake::TestTask.new(:ios) do |t|
41
+ ENV['UNIT_TEST'] = '1'
41
42
  t.libs << 'test'
42
43
  t.libs << 'lib'
43
44
  t.test_files = FileList['test/unit/ios/**/*_test.rb']
@@ -45,6 +46,7 @@ namespace :test do
45
46
 
46
47
  desc('Run all Android related unit tests in test directory')
47
48
  Rake::TestTask.new(:android) do |t|
49
+ ENV['UNIT_TEST'] = '1'
48
50
  t.libs << 'test'
49
51
  t.libs << 'lib'
50
52
  t.test_files = FileList['test/unit/android/**/*_test.rb']
@@ -52,6 +54,7 @@ namespace :test do
52
54
 
53
55
  desc('Run all common related unit tests in test directory')
54
56
  Rake::TestTask.new(:common) do |t|
57
+ ENV['UNIT_TEST'] = '1'
55
58
  t.libs << 'test'
56
59
  t.libs << 'lib'
57
60
  t.test_files = FileList['test/unit/common/**/*_test.rb']
@@ -59,6 +62,7 @@ namespace :test do
59
62
 
60
63
  desc('Run all Windows related unit tests in test directory')
61
64
  Rake::TestTask.new(:windows) do |t|
65
+ ENV['UNIT_TEST'] = '1'
62
66
  t.libs << 'test'
63
67
  t.libs << 'lib'
64
68
  t.test_files = FileList['test/unit/windows/**/*_test.rb']
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.add_runtime_dependency 'selenium-webdriver', '4.0.0.beta2'
25
+ spec.add_runtime_dependency 'selenium-webdriver', '4.0.0.rc1'
26
26
  spec.add_runtime_dependency 'faye-websocket', '~> 0.11.0'
27
27
 
28
28
  spec.add_development_dependency 'bundler', '>= 1.14'
@@ -30,8 +30,8 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'yard', '~> 0.9.11'
31
31
  spec.add_development_dependency 'minitest', '~> 5.0'
32
32
  spec.add_development_dependency 'minitest-reporters', '~> 1.1'
33
- spec.add_development_dependency 'webmock', '~> 3.12.1'
34
- spec.add_development_dependency 'rubocop', '1.11.0'
33
+ spec.add_development_dependency 'webmock', '~> 3.14.0'
34
+ spec.add_development_dependency 'rubocop', '1.12.1'
35
35
  spec.add_development_dependency 'appium_thor', '~> 1.0'
36
36
  spec.add_development_dependency 'pry'
37
37
  spec.add_development_dependency 'pry-byebug'
@@ -103,7 +103,7 @@ jobs:
103
103
  - template: ./functional/ios_setup.yml
104
104
  parameters:
105
105
  xcodeVersion: ${{ parameters.xcodeForIOS }}
106
- - script: brew install ffmpeg && brew tap wix/brew && brew install wix/brew/applesimutils
106
+ - script: brew install ffmpeg && brew tap wix/brew && brew install applesimutils
107
107
  displayName: Install ffmpeg and applesimutils
108
108
  - template: ./functional/run_appium.yml
109
109
  - script: bundle exec rake test:func:ios TESTS=test/functional/ios/ios/mjpeg_server_test.rb,test/functional/ios/ios/mobile_commands_test.rb
@@ -9,7 +9,7 @@ We can invoke them via `execute_script` command with `mobile:` arguments.
9
9
  - https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/android
10
10
  - iOS:
11
11
  - https://github.com/appium/appium/tree/master/docs/en/writing-running-appium/ios
12
-
12
+
13
13
 
14
14
  ```ruby
15
15
  # Android shell : https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/android/android-shell.md
@@ -28,7 +28,8 @@ To handle it, we would recommend you to handle the error based on the error mess
28
28
 
29
29
  ```ruby
30
30
  error = assert_raises ::Selenium::WebDriver::Error::UnknownError do
31
- @driver.execute_script 'mobile: scrollToPage', { element: el.ref, scrollToPage: -100 }
31
+ _, element_id = el.ref
32
+ @driver.execute_script 'mobile: scrollToPage', { element: element_id, scrollToPage: -100 }
32
33
  end
33
34
  assert error.message.include? 'be a non-negative integer'
34
35
  ```
@@ -21,7 +21,8 @@ module Appium
21
21
  ::Appium::Core::Device.add_endpoint_method(:finger_print) do
22
22
  def finger_print(finger_id)
23
23
  unless (1..10).cover? finger_id.to_i
24
- raise ArgumentError, "finger_id should be integer between 1 to 10. Not #{finger_id}"
24
+ raise ::Appium::Core::Error::ArgumentError,
25
+ "finger_id should be integer between 1 to 10. Not #{finger_id}"
25
26
  end
26
27
 
27
28
  execute(:finger_print, {}, { fingerprintId: finger_id.to_i })
@@ -16,11 +16,40 @@ module Appium
16
16
  module Core
17
17
  class Base
18
18
  class Bridge < ::Selenium::WebDriver::Remote::Bridge
19
- # TODO: Move W3C module to here
19
+ include Device::DeviceLock
20
+ include Device::Keyboard
21
+ include Device::ImeActions
22
+ include Device::Setting
23
+ include Device::Context
24
+ include Device::Value
25
+ include Device::FileManagement
26
+ include Device::KeyEvent
27
+ include Device::ImageComparison
28
+ include Device::AppManagement
29
+ include Device::AppState
30
+ include Device::ScreenRecord::Command
31
+ include Device::Device
32
+ include Device::TouchActions
33
+ include Device::ExecuteDriver
34
+ include Device::Orientation
20
35
 
21
36
  # Prefix for extra capability defined by W3C
22
37
  APPIUM_PREFIX = 'appium:'
23
38
 
39
+ # No 'browserName' means the session is native appium connection
40
+ APPIUM_NATIVE_BROWSER_NAME = 'appium'
41
+
42
+ attr_reader :available_commands
43
+
44
+ def browser
45
+ @browser ||= begin
46
+ name = @capabilities.browser_name
47
+ name ? name.tr(' ', '_').downcase.to_sym : 'unknown'
48
+ rescue KeyError
49
+ APPIUM_NATIVE_BROWSER_NAME
50
+ end
51
+ end
52
+
24
53
  # Override
25
54
  # Creates session handling both OSS and W3C dialects.
26
55
  #
@@ -45,16 +74,16 @@ module Appium
45
74
  # core = ::Appium::Core.for(caps)
46
75
  # driver = core.start_driver
47
76
  #
48
- def create_session(desired_capabilities)
49
- caps = add_appium_prefix(desired_capabilities)
77
+ def create_session(capabilities)
78
+ @available_commands = ::Appium::Core::Commands::COMMANDS.dup
79
+
80
+ caps = add_appium_prefix(capabilities)
50
81
  response = execute(:new_session, {}, { capabilities: { firstMatch: [caps] } })
51
82
 
52
83
  @session_id = response['sessionId']
53
- capabilities = response['capabilities']
54
-
55
84
  raise ::Selenium::WebDriver::Error::WebDriverError, 'no sessionId in returned payload' unless @session_id
56
85
 
57
- @capabilities = json_create(capabilities)
86
+ @capabilities = json_create(response['capabilities'])
58
87
  end
59
88
 
60
89
  # Append +appium:+ prefix for Appium following W3C spec
@@ -65,7 +94,7 @@ module Appium
65
94
  def add_appium_prefix(capabilities)
66
95
  w3c_capabilities = ::Selenium::WebDriver::Remote::Capabilities.new
67
96
 
68
- capabilities = capabilities.__send__(:capabilities) unless capabilities.is_a?(Hash)
97
+ capabilities = capabilities.send(:capabilities) unless capabilities.is_a?(Hash)
69
98
 
70
99
  capabilities.each do |name, value|
71
100
  next if value.nil?
@@ -99,6 +128,273 @@ module Appium
99
128
  def json_create(value)
100
129
  ::Selenium::WebDriver::Remote::Capabilities.json_create(value)
101
130
  end
131
+
132
+ public
133
+
134
+ # command for Appium 2.0.
135
+ def add_command(method:, url:, name:, &block)
136
+ ::Appium::Logger.info "Overriding the method '#{name}' for '#{url}'" if @available_commands.key? name
137
+
138
+ @available_commands[name] = [method, url]
139
+
140
+ ::Appium::Core::Device.add_endpoint_method name, &block
141
+ end
142
+
143
+ def commands(command)
144
+ @available_commands[command]
145
+ end
146
+
147
+ # Returns all available sessions on the Appium server instance
148
+ def sessions
149
+ execute :get_all_sessions
150
+ end
151
+
152
+ def status
153
+ execute :status
154
+ end
155
+
156
+ # Perform touch actions for W3C module.
157
+ # Generate +touch+ pointer action here and users can use this via +driver.action+
158
+ # - https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/W3CActionBuilder.html
159
+ # - https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/PointerActions.html
160
+ # - https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/KeyActions.html
161
+ #
162
+ # 'mouse' action is by default in the Ruby client. Appium server force the +mouse+ action to +touch+ once in
163
+ # the server side. So we don't consider the case.
164
+ #
165
+ # @example
166
+ #
167
+ # element = @driver.find_element(:id, "some id")
168
+ # @driver.action.click(element).perform # The 'click' is a part of 'PointerActions'
169
+ #
170
+ def action(async = false)
171
+ # Used for default duration of each touch actions
172
+ # Override from 250 milliseconds to 50 milliseconds
173
+ action_builder = super
174
+ action_builder.default_move_duration = 0.05
175
+ action_builder
176
+ end
177
+
178
+ # Port from MJSONWP
179
+ def get_timeouts
180
+ execute :get_timeouts
181
+ end
182
+
183
+ # Port from MJSONWP
184
+ def session_capabilities
185
+ ::Selenium::WebDriver::Remote::Capabilities.json_create execute(:get_capabilities)
186
+ end
187
+
188
+ # Port from MJSONWP
189
+ def send_keys_to_active_element(key)
190
+ text = ::Selenium::WebDriver::Keys.encode(key).join('')
191
+ execute :send_keys_to_active_element, {}, { value: text.split(//) }
192
+ end
193
+
194
+ # For Appium
195
+ # override
196
+ def page_source
197
+ # For W3C
198
+ # execute_script('var source = document.documentElement.outerHTML;' \
199
+ # 'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
200
+ # 'return source;')
201
+ execute :get_page_source
202
+ end
203
+
204
+ # For Appium
205
+ # override
206
+ def element_displayed?(element)
207
+ # For W3C
208
+ # https://github.com/SeleniumHQ/selenium/commit/b618499adcc3a9f667590652c5757c0caa703289
209
+ # execute_atom :isDisplayed, element
210
+ _, element_id = element.ref
211
+ execute :is_element_displayed, id: element_id
212
+ end
213
+
214
+ # For Appium
215
+ # override
216
+ def element_attribute(element, name)
217
+ # For W3C in Selenium Client
218
+ # execute_atom :getAttribute, element, name
219
+ _, element_id = element.ref
220
+ execute :get_element_attribute, id: element_id, name: name
221
+ end
222
+
223
+ # For Appium
224
+ # override
225
+ def active_element
226
+ ::Appium::Core::Element.new self, element_id_from(execute(:get_active_element))
227
+ end
228
+ alias switch_to_active_element active_element
229
+
230
+ # For Appium
231
+ # override
232
+ def find_element_by(how, what, parent_ref = [])
233
+ how, what = convert_locator(how, what)
234
+
235
+ return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
236
+
237
+ parent_type, parent_id = parent_ref
238
+ id = case parent_type
239
+ when :element
240
+ execute :find_child_element, { id: parent_id }, { using: how, value: what.to_s }
241
+ when :shadow_root
242
+ execute :find_shadow_child_element, { id: parent_id }, { using: how, value: what.to_s }
243
+ else
244
+ execute :find_element, {}, { using: how, value: what.to_s }
245
+ end
246
+
247
+ ::Appium::Core::Element.new self, element_id_from(id)
248
+ end
249
+
250
+ # For Appium
251
+ # override
252
+ def find_elements_by(how, what, parent_ref = [])
253
+ how, what = convert_locator(how, what)
254
+
255
+ return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
256
+
257
+ parent_type, parent_id = parent_ref
258
+ ids = case parent_type
259
+ when :element
260
+ execute :find_child_elements, { id: parent_id }, { using: how, value: what.to_s }
261
+ when :shadow_root
262
+ execute :find_shadow_child_elements, { id: parent_id }, { using: how, value: what.to_s }
263
+ else
264
+ execute :find_elements, {}, { using: how, value: what.to_s }
265
+ end
266
+
267
+ ids.map { |id| ::Appium::Core::Element.new self, element_id_from(id) }
268
+ end
269
+
270
+ # For Appium
271
+ # @param [Hash] id The id which can get as a response from server
272
+ # @return [::Appium::Core::Element]
273
+ def convert_to_element(id)
274
+ ::Appium::Core::Element.new self, element_id_from(id)
275
+ end
276
+
277
+ # For Appium
278
+ # override
279
+ # called in 'extend DriverExtensions::HasNetworkConnection'
280
+ def network_connection
281
+ execute :get_network_connection
282
+ end
283
+
284
+ # For Appium
285
+ # override
286
+ # called in 'extend DriverExtensions::HasNetworkConnection'
287
+ def network_connection=(type)
288
+ execute :set_network_connection, {}, { parameters: { type: type } }
289
+ end
290
+
291
+ # For Appium
292
+ # No implementation for W3C webdriver module
293
+ # called in 'extend DriverExtensions::HasLocation'
294
+ def location
295
+ obj = execute(:get_location) || {}
296
+ ::Selenium::WebDriver::Location.new obj['latitude'], obj['longitude'], obj['altitude']
297
+ end
298
+
299
+ # For Appium
300
+ # No implementation for W3C webdriver module
301
+ def set_location(lat, lon, alt = 0.0, speed: nil, satellites: nil)
302
+ loc = { latitude: lat, longitude: lon, altitude: alt }
303
+ loc[:speed] = speed unless speed.nil?
304
+ loc[:satellites] = satellites unless satellites.nil?
305
+ execute :set_location, {}, { location: loc }
306
+ end
307
+
308
+ #
309
+ # logs
310
+ #
311
+ # For Appium
312
+ # No implementation for W3C webdriver module
313
+ def available_log_types
314
+ types = execute :get_available_log_types
315
+ Array(types).map(&:to_sym)
316
+ end
317
+
318
+ # For Appium
319
+ # No implementation for W3C webdriver module
320
+ def log(type)
321
+ data = execute :get_log, {}, { type: type.to_s }
322
+
323
+ Array(data).map do |l|
324
+ begin
325
+ ::Selenium::WebDriver::LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message')
326
+ rescue KeyError
327
+ next
328
+ end
329
+ end
330
+ end
331
+
332
+ # For Appium
333
+ def log_event(vendor, event)
334
+ execute :post_log_event, {}, { vendor: vendor, event: event }
335
+ end
336
+
337
+ # For Appium
338
+ def log_events(type = nil)
339
+ args = {}
340
+ args['type'] = type unless type.nil?
341
+
342
+ execute :get_log_events, {}, args
343
+ end
344
+
345
+ def viewport_screenshot
346
+ execute_script('mobile: viewportScreenshot')
347
+ end
348
+
349
+ def element_screenshot(element_id)
350
+ execute :take_element_screenshot, id: element_id
351
+ end
352
+
353
+ private
354
+
355
+ def unwrap_script_result(arg)
356
+ case arg
357
+ when Array
358
+ arg.map { |e| unwrap_script_result(e) }
359
+ when Hash
360
+ element_id = element_id_from(arg)
361
+ return ::Appium::Core::Element.new(self, element_id) if element_id
362
+
363
+ arg.each { |k, v| arg[k] = unwrap_script_result(v) }
364
+ else
365
+ arg
366
+ end
367
+ end
368
+
369
+ def element_id_from(id)
370
+ id['ELEMENT'] || id['element-6066-11e4-a52e-4f735466cecf']
371
+ end
372
+
373
+ # Don't convert locators for Appium in native context
374
+ def convert_locator(how, what)
375
+ # case how
376
+ # when 'class name'
377
+ # how = 'css selector'
378
+ # what = ".#{escape_css(what)}"
379
+ # when 'id'
380
+ # how = 'css selector'
381
+ # what = "##{escape_css(what)}"
382
+ # when 'name'
383
+ # how = 'css selector'
384
+ # what = "*[name='#{escape_css(what)}']"
385
+ # when 'tag name'
386
+ # how = 'css selector'
387
+ # end
388
+ #
389
+ # if what.is_a?(Hash)
390
+ # what = what.each_with_object({}) do |(h, w), hash|
391
+ # h, w = convert_locator(h.to_s, w)
392
+ # hash[h] = w
393
+ # end
394
+ # end
395
+
396
+ [how, what]
397
+ end
102
398
  end # class Bridge
103
399
  end # class Base
104
400
  end # module Core