automation_object 0.6.0 → 0.7.5
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/lib/automation_object/blue_print/hash_adapter/hook_action.rb +1 -1
- data/lib/automation_object/driver/appium_adapter/driver.rb +17 -1
- data/lib/automation_object/driver/driver.rb +7 -1
- data/lib/automation_object/driver/nokogiri_adapter/driver.rb +6 -0
- data/lib/automation_object/driver/selenium_adapter/driver.rb +16 -1
- data/lib/automation_object/dsl/_base.rb +6 -3
- data/lib/automation_object/dsl/_proxy.rb +2 -2
- data/lib/automation_object/dsl/element.rb +9 -3
- data/lib/automation_object/dsl/element_array.rb +9 -3
- data/lib/automation_object/dsl/element_hash.rb +9 -3
- data/lib/automation_object/dsl/modal.rb +9 -6
- data/lib/automation_object/dsl/screen.rb +5 -7
- data/lib/automation_object/dsl/top.rb +7 -3
- data/lib/automation_object/state/_base.rb +72 -0
- data/lib/automation_object/state/_common_element.rb +44 -0
- data/lib/automation_object/state/{error.rb → _error.rb} +4 -0
- data/lib/automation_object/state/element.rb +23 -0
- data/lib/automation_object/state/element_array.rb +26 -0
- data/lib/automation_object/state/element_hash.rb +34 -0
- data/lib/automation_object/state/element_proxy.rb +31 -0
- data/lib/automation_object/state/hook.rb +51 -0
- data/lib/automation_object/state/{composite/hook_action.rb → hook_action.rb} +11 -13
- data/lib/automation_object/state/hook_actions/action_loop.rb +43 -0
- data/lib/automation_object/state/hook_actions/change_screen.rb +23 -0
- data/lib/automation_object/state/hook_actions/change_to_previous_screen.rb +23 -0
- data/lib/automation_object/state/hook_actions/close_modal.rb +21 -0
- data/lib/automation_object/state/hook_actions/close_screen.rb +20 -0
- data/lib/automation_object/state/hook_actions/element_requirement.rb +30 -0
- data/lib/automation_object/state/hook_actions/new_screen.rb +49 -0
- data/lib/automation_object/state/hook_actions/possible_screen_changes.rb +23 -0
- data/lib/automation_object/state/{composite/hook_actions → hook_actions}/reset_screen.rb +4 -5
- data/lib/automation_object/state/hook_actions/show_modal.rb +18 -0
- data/lib/automation_object/state/hook_actions/sleep.rb +13 -0
- data/lib/automation_object/state/hook_actions/wait_for_elements.rb +18 -0
- data/lib/automation_object/state/modal.rb +47 -0
- data/lib/automation_object/state/screen.rb +70 -0
- data/lib/automation_object/state/top.rb +43 -0
- data/lib/automation_object/state.rb +3 -3
- data/lib/automation_object/version.rb +1 -1
- metadata +40 -43
- data/lib/automation_object/state/composite/_base.rb +0 -81
- data/lib/automation_object/state/composite/_common_element.rb +0 -38
- data/lib/automation_object/state/composite/element.rb +0 -24
- data/lib/automation_object/state/composite/element_array.rb +0 -27
- data/lib/automation_object/state/composite/element_hash.rb +0 -35
- data/lib/automation_object/state/composite/element_proxy.rb +0 -33
- data/lib/automation_object/state/composite/helpers/window.rb +0 -79
- data/lib/automation_object/state/composite/helpers/window_manager.rb +0 -59
- data/lib/automation_object/state/composite/hook.rb +0 -55
- data/lib/automation_object/state/composite/hook_actions/action_loop.rb +0 -45
- data/lib/automation_object/state/composite/hook_actions/change_screen.rb +0 -23
- data/lib/automation_object/state/composite/hook_actions/change_to_previous_screen.rb +0 -23
- data/lib/automation_object/state/composite/hook_actions/close_modal.rb +0 -29
- data/lib/automation_object/state/composite/hook_actions/close_screen.rb +0 -19
- data/lib/automation_object/state/composite/hook_actions/element_requirement.rb +0 -32
- data/lib/automation_object/state/composite/hook_actions/new_screen.rb +0 -31
- data/lib/automation_object/state/composite/hook_actions/possible_screen_changes.rb +0 -22
- data/lib/automation_object/state/composite/hook_actions/show_modal.rb +0 -23
- data/lib/automation_object/state/composite/hook_actions/sleep.rb +0 -15
- data/lib/automation_object/state/composite/hook_actions/wait_for_elements.rb +0 -20
- data/lib/automation_object/state/composite/modal.rb +0 -45
- data/lib/automation_object/state/composite/screen.rb +0 -64
- data/lib/automation_object/state/composite/top.rb +0 -51
- data/lib/automation_object/state/session.rb +0 -54
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb439ab990fae3fa652a146ce651524dfebc2e48
|
4
|
+
data.tar.gz: 258df7188016240e3e41a3879af8b6554e06afa2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49e8cfc6f45c6baaa6cda9cdd4b27b8184af332791c69309411fcb391389b767e5cd9d55d068b09a9c1fd948b69188a6075400648e31b10b130004149637846b
|
7
|
+
data.tar.gz: 821123032d2a696c7cf12b7b1a15e8d700adecde0259f63db9c828fcd4c64237bb1fbc714a32c26b1b27e4e1f22205d633d9c0599c2a2ab657198cd42e155db4
|
@@ -21,7 +21,7 @@ module AutomationObject
|
|
21
21
|
validates :change_to_previous_screen, instance_of: TrueClass
|
22
22
|
validates :close_modal, instance_of: TrueClass
|
23
23
|
validates :close_screen, instance_of: TrueClass
|
24
|
-
validates :possible_screen_changes, instance_of:
|
24
|
+
validates :possible_screen_changes, instance_of: Array, screen_presence_of: true
|
25
25
|
validates :reset_screen, instance_of: TrueClass
|
26
26
|
validates :show_modal, instance_of: String, modal_presence_of: true
|
27
27
|
validates :sleep, instance_of: Numeric
|
@@ -17,6 +17,9 @@ module AutomationObject
|
|
17
17
|
class Driver < AutomationObject::Proxy::Proxy
|
18
18
|
include AutomationObject::Driver::CommonSelenium::Driver
|
19
19
|
|
20
|
+
DOCUMENT_COMPLETE_LOOPS = 5
|
21
|
+
DOCUMENT_COMPLETE_SLEEP = 0.2
|
22
|
+
|
20
23
|
# @param driver [Appium::Driver] Appium Driver
|
21
24
|
def initialize(driver)
|
22
25
|
@subject = driver
|
@@ -138,7 +141,20 @@ module AutomationObject
|
|
138
141
|
# @return [Boolean] document is complete
|
139
142
|
def document_complete?
|
140
143
|
return true unless browser? # Skip for non-browser Appium sessions
|
141
|
-
|
144
|
+
|
145
|
+
# Loop through a few times to double check correctness
|
146
|
+
DOCUMENT_COMPLETE_LOOPS.times do
|
147
|
+
sleep(DOCUMENT_COMPLETE_SLEEP)
|
148
|
+
return false unless @subject.execute_script('return document.readyState;') == 'complete'
|
149
|
+
end
|
150
|
+
|
151
|
+
true
|
152
|
+
end
|
153
|
+
|
154
|
+
# Close current window
|
155
|
+
# @return [void]
|
156
|
+
def close
|
157
|
+
@subject.close
|
142
158
|
end
|
143
159
|
|
144
160
|
# @return [void]
|
@@ -86,6 +86,12 @@ module AutomationObject
|
|
86
86
|
adapter.browser?
|
87
87
|
end
|
88
88
|
|
89
|
+
# Close current window
|
90
|
+
# @return [void]
|
91
|
+
def close
|
92
|
+
adapter.close
|
93
|
+
end
|
94
|
+
|
89
95
|
# Window Handles
|
90
96
|
# @return [Array<String>] array of window handle ids
|
91
97
|
def window_handles
|
@@ -101,7 +107,7 @@ module AutomationObject
|
|
101
107
|
# Set current window handle to, will switch windows
|
102
108
|
# @param handle_value [String] window handle value
|
103
109
|
def window_handle=(handle_value)
|
104
|
-
adapter.window_handle
|
110
|
+
adapter.window_handle = handle_value
|
105
111
|
end
|
106
112
|
|
107
113
|
# Run script in browser to check if document in JS is complete
|
@@ -15,6 +15,9 @@ module AutomationObject
|
|
15
15
|
class Driver < AutomationObject::Proxy::Proxy
|
16
16
|
include AutomationObject::Driver::CommonSelenium::Driver
|
17
17
|
|
18
|
+
DOCUMENT_COMPLETE_LOOPS = 5
|
19
|
+
DOCUMENT_COMPLETE_SLEEP = 0.2
|
20
|
+
|
18
21
|
# @param driver [Selenium::WebDriver::Driver] Selenium Web Driver
|
19
22
|
def initialize(driver)
|
20
23
|
@subject = driver
|
@@ -117,10 +120,22 @@ module AutomationObject
|
|
117
120
|
@subject.switch_to.window(handle_value)
|
118
121
|
end
|
119
122
|
|
123
|
+
# Close current window
|
124
|
+
# @return [void]
|
125
|
+
def close
|
126
|
+
@subject.close
|
127
|
+
end
|
128
|
+
|
120
129
|
# Run script in browser to check if document in JS is complete
|
121
130
|
# @return [Boolean] document is complete
|
122
131
|
def document_complete?
|
123
|
-
|
132
|
+
# Loop through a few times to double check correctness
|
133
|
+
DOCUMENT_COMPLETE_LOOPS.times do
|
134
|
+
sleep(DOCUMENT_COMPLETE_SLEEP)
|
135
|
+
return false unless @subject.execute_script('return document.readyState;') == 'complete'
|
136
|
+
end
|
137
|
+
|
138
|
+
true
|
124
139
|
end
|
125
140
|
|
126
141
|
# Destroy the driver
|
@@ -19,6 +19,10 @@ module AutomationObject
|
|
19
19
|
super ostruct_hash
|
20
20
|
end
|
21
21
|
|
22
|
+
def to_s
|
23
|
+
inspect
|
24
|
+
end
|
25
|
+
|
22
26
|
# @param _indent [Integer]
|
23
27
|
# @return [String]
|
24
28
|
def inspect(_indent = 5)
|
@@ -36,9 +40,8 @@ module AutomationObject
|
|
36
40
|
# @param indent [Integer]
|
37
41
|
# @return [String]
|
38
42
|
def formatted_name(key, indent)
|
39
|
-
|
40
|
-
#
|
41
|
-
"\n#{' ' * indent} #{key}:".colorize(:green)
|
43
|
+
color = self[key].active? ? :green : :blue
|
44
|
+
"\n#{' ' * indent} #{key}:".colorize(color)
|
42
45
|
end
|
43
46
|
|
44
47
|
# @param value [Object]
|
@@ -8,8 +8,8 @@ module AutomationObject
|
|
8
8
|
# Prevents inspection from picking up internal methods
|
9
9
|
class Proxy < AutomationObject::Proxy::Proxy
|
10
10
|
# @param subject_class [AutomationObject::Dsl::CompositeBase, nil]
|
11
|
-
# @param blue_prints [AutomationObject::BluePrint::Composite::
|
12
|
-
# @param state [AutomationObject::State::
|
11
|
+
# @param blue_prints [AutomationObject::BluePrint::Composite::Base]
|
12
|
+
# @param state [AutomationObject::State::Base]
|
13
13
|
# @param name [Symbol]
|
14
14
|
def initialize(subject_class, blue_prints, state, name)
|
15
15
|
@blue_prints = blue_prints
|
@@ -10,10 +10,11 @@ module AutomationObject
|
|
10
10
|
# Proxy for Element
|
11
11
|
class ElementProxy < Proxy
|
12
12
|
# @param [AutomationObject::BluePrint::Composite::Element] blue_prints
|
13
|
-
# @param [AutomationObject::State::
|
13
|
+
# @param [AutomationObject::State::Modal, AutomationObject::State::Screen] state
|
14
14
|
# @param [Symbol] name
|
15
15
|
def initialize(blue_prints, state, name)
|
16
|
-
|
16
|
+
element_state = state.elements[name]
|
17
|
+
super Element, blue_prints, element_state, name
|
17
18
|
end
|
18
19
|
|
19
20
|
# @param [Symbol] method
|
@@ -22,10 +23,15 @@ module AutomationObject
|
|
22
23
|
def method_missing(method, *args, &block)
|
23
24
|
return super if Element.methods.include?(method)
|
24
25
|
|
25
|
-
@subject = @state.
|
26
|
+
@subject = @state.utilize
|
26
27
|
|
27
28
|
super
|
28
29
|
end
|
30
|
+
|
31
|
+
# @return [Boolean]
|
32
|
+
def active?
|
33
|
+
@state.active?
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -10,10 +10,11 @@ module AutomationObject
|
|
10
10
|
# Proxy for ElementArray
|
11
11
|
class ElementArrayProxy < Proxy
|
12
12
|
# @param [AutomationObject::BluePrint::Composite::ElementArray] blue_prints
|
13
|
-
# @param [AutomationObject::State::
|
13
|
+
# @param [AutomationObject::State::Modal, AutomationObject::State::Screen] state
|
14
14
|
# @param [Symbol] name
|
15
15
|
def initialize(blue_prints, state, name)
|
16
|
-
|
16
|
+
element_array_state = state.element_arrays[name]
|
17
|
+
super ElementArray, blue_prints, element_array_state, name
|
17
18
|
end
|
18
19
|
|
19
20
|
# @param [Symbol] method
|
@@ -22,10 +23,15 @@ module AutomationObject
|
|
22
23
|
def method_missing(method, *args, &block)
|
23
24
|
return super if ElementArray.methods.include?(method)
|
24
25
|
|
25
|
-
@subject = @state.
|
26
|
+
@subject = @state.utilize
|
26
27
|
|
27
28
|
super
|
28
29
|
end
|
30
|
+
|
31
|
+
# @return [Boolean]
|
32
|
+
def active?
|
33
|
+
@state.active?
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -10,10 +10,11 @@ module AutomationObject
|
|
10
10
|
# Proxy for ElementHash
|
11
11
|
class ElementHashProxy < Proxy
|
12
12
|
# @param [AutomationObject::BluePrint::Composite::ElementHash] blue_prints
|
13
|
-
# @param [AutomationObject::State::
|
13
|
+
# @param [AutomationObject::State::Modal, AutomationObject::State::Screen] state
|
14
14
|
# @param [Symbol] name
|
15
15
|
def initialize(blue_prints, state, name)
|
16
|
-
|
16
|
+
element_hash_state = state.element_hashes[name]
|
17
|
+
super ElementHash, blue_prints, element_hash_state, name
|
17
18
|
end
|
18
19
|
|
19
20
|
# @param [Symbol] method
|
@@ -22,10 +23,15 @@ module AutomationObject
|
|
22
23
|
def method_missing(method, *args, &block)
|
23
24
|
return super if ElementHash.methods.include?(method)
|
24
25
|
|
25
|
-
@subject = @state.
|
26
|
+
@subject = @state.utilize
|
26
27
|
|
27
28
|
super
|
28
29
|
end
|
30
|
+
|
31
|
+
# @return [Boolean]
|
32
|
+
def active?
|
33
|
+
@state.active?
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -18,10 +18,11 @@ module AutomationObject
|
|
18
18
|
# Proxy for Modal
|
19
19
|
class ModalProxy < Proxy
|
20
20
|
# @param [AutomationObject::BluePrint::Composite::Modal] blue_prints
|
21
|
-
# @param [AutomationObject::State::
|
21
|
+
# @param [AutomationObject::State::Screen] state
|
22
22
|
# @param [Symbol] name
|
23
23
|
def initialize(blue_prints, state, name)
|
24
|
-
|
24
|
+
modal_state = state.modals[name]
|
25
|
+
super Modal, blue_prints, modal_state, name
|
25
26
|
end
|
26
27
|
|
27
28
|
# @param [Symbol] method
|
@@ -31,13 +32,15 @@ module AutomationObject
|
|
31
32
|
return super if Modal.methods.include?(method)
|
32
33
|
|
33
34
|
# Attempt to load modal if composite object contains that child
|
34
|
-
if @subject.to_h.include?(method)
|
35
|
-
# puts "@state.load(:modal, #{@name})"
|
36
|
-
@state.load(:modal, @name)
|
37
|
-
end
|
35
|
+
@state.utilize if @subject.to_h.include?(method)
|
38
36
|
|
39
37
|
super
|
40
38
|
end
|
39
|
+
|
40
|
+
# @return [Boolean]
|
41
|
+
def active?
|
42
|
+
@state.active?
|
43
|
+
end
|
41
44
|
end
|
42
45
|
end
|
43
46
|
end
|
@@ -20,10 +20,11 @@ module AutomationObject
|
|
20
20
|
# Proxy for Screen
|
21
21
|
class ScreenProxy < Proxy
|
22
22
|
# @param [AutomationObject::BluePrint::Composite::Screen] blue_prints
|
23
|
-
# @param [AutomationObject::State::
|
23
|
+
# @param [AutomationObject::State::Top] top state
|
24
24
|
# @param [Symbol] name
|
25
25
|
def initialize(blue_prints, state, name)
|
26
|
-
|
26
|
+
screen_state = state.screens[name]
|
27
|
+
super Screen, blue_prints, screen_state, name
|
27
28
|
end
|
28
29
|
|
29
30
|
# @param [Symbol] method
|
@@ -33,17 +34,14 @@ module AutomationObject
|
|
33
34
|
return super if Screen.methods.include?(method)
|
34
35
|
|
35
36
|
# Attempt to load screen if composite object contains that child
|
36
|
-
if @subject.to_h.include?(method)
|
37
|
-
# puts "@state.load(:screen, #{@name})"
|
38
|
-
@state.load(:screen, @name)
|
39
|
-
end
|
37
|
+
@state.utilize if @subject.to_h.include?(method)
|
40
38
|
|
41
39
|
super
|
42
40
|
end
|
43
41
|
|
44
42
|
# @return [Boolean]
|
45
43
|
def active?
|
46
|
-
@state.active?
|
44
|
+
@state.active?
|
47
45
|
end
|
48
46
|
end
|
49
47
|
end
|
@@ -14,12 +14,16 @@ module AutomationObject
|
|
14
14
|
# Proxy for Top Composite
|
15
15
|
# Use proxy for methods trying to do a @state call
|
16
16
|
class TopProxy < Proxy
|
17
|
-
# @param [AutomationObject::BluePrint::Composite::Top]
|
18
|
-
# @param [AutomationObject::State::
|
19
|
-
# @param [Symbol]
|
17
|
+
# @param blue_prints [AutomationObject::BluePrint::Composite::Top]
|
18
|
+
# @param state [AutomationObject::State::Top] top composite
|
19
|
+
# @param name [Symbol]
|
20
20
|
def initialize(blue_prints, state, name)
|
21
21
|
super Top, blue_prints, state, name
|
22
22
|
end
|
23
|
+
|
24
|
+
def active?
|
25
|
+
true
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative '../helpers/composite'
|
3
|
+
|
4
|
+
module AutomationObject
|
5
|
+
module State
|
6
|
+
# Parent composite class
|
7
|
+
class Base < ::AutomationObject::Composite
|
8
|
+
# @return [AutomationObject::Driver::Driver]
|
9
|
+
attr_accessor :driver
|
10
|
+
|
11
|
+
# @return [AutomationObject::BluePrint::Composite::Base]
|
12
|
+
attr_accessor :blue_prints
|
13
|
+
|
14
|
+
# @param driver [AutomationObject::Driver] driver
|
15
|
+
# @param blue_prints [AutomationObject::BluePrint::Composite::Base] blue print composite
|
16
|
+
# @param name [Symbol] name of composite element
|
17
|
+
# @param parent [Object, nil] parent composite object
|
18
|
+
# @param location [String] string location for error/debugging purposes
|
19
|
+
def initialize(driver, blue_prints, name = :top, parent = nil, location = 'top')
|
20
|
+
self.driver = driver
|
21
|
+
self.blue_prints = blue_prints
|
22
|
+
|
23
|
+
super(name, parent, location)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Overriding base get_child method
|
27
|
+
# @param name [Symbol] name of child
|
28
|
+
# @param args [Hash] args
|
29
|
+
def get_child(name, args)
|
30
|
+
child_location = location + "[#{name}]"
|
31
|
+
|
32
|
+
args.fetch(:interface).new(driver,
|
33
|
+
blue_prints.send(name),
|
34
|
+
name, self, child_location)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Overriding base get_children method
|
38
|
+
# @param name [Symbol] name of child
|
39
|
+
# @param args [Hash] args
|
40
|
+
# @return children [Array<Composite>] return children and names
|
41
|
+
def get_children(name, args)
|
42
|
+
children_hash = {}
|
43
|
+
|
44
|
+
blue_prints.send(name).each do |child_key, child_blue_prints|
|
45
|
+
child_location = location + "[#{child_key}]"
|
46
|
+
|
47
|
+
children_hash[child_key] = args.fetch(:interface).new(driver,
|
48
|
+
child_blue_prints,
|
49
|
+
name, self, child_location)
|
50
|
+
end
|
51
|
+
|
52
|
+
children_hash
|
53
|
+
end
|
54
|
+
|
55
|
+
# Recursive function to reach parent screen
|
56
|
+
# Can return nil if above a screen!
|
57
|
+
# @return [AutomationObject::State::Screen,nil]
|
58
|
+
def screen
|
59
|
+
return nil if is_a?(Top)
|
60
|
+
|
61
|
+
# Should recursively call top until parent is nil
|
62
|
+
is_a?(Screen) ? self : parent.screen
|
63
|
+
end
|
64
|
+
|
65
|
+
# Recursive function to reach top
|
66
|
+
# @return [AutomationObject::State::Top]
|
67
|
+
def top
|
68
|
+
is_a?(Top) ? self : parent.top
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'hook'
|
3
|
+
|
4
|
+
module AutomationObject
|
5
|
+
module State
|
6
|
+
# Helper module for Element composite classes
|
7
|
+
module CommonElement
|
8
|
+
attr_accessor :cache
|
9
|
+
|
10
|
+
# Whether or not modal is active
|
11
|
+
attr_accessor :active
|
12
|
+
|
13
|
+
# @return [Boolean] screen is active or not
|
14
|
+
def active?
|
15
|
+
@active ||= false
|
16
|
+
end
|
17
|
+
|
18
|
+
def reset
|
19
|
+
@active = false
|
20
|
+
self.cache = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_hook?(name)
|
24
|
+
blue_prints.method_hooks.key?(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Hash<Hook>] array of Hook that are defined under the element
|
28
|
+
def method_hooks
|
29
|
+
return @method_hooks if @method_hooks
|
30
|
+
|
31
|
+
@method_hooks = {}
|
32
|
+
blue_prints.method_hooks.each do |key, blue_prints|
|
33
|
+
@method_hooks[key] = Hook.new(driver,
|
34
|
+
blue_prints,
|
35
|
+
key,
|
36
|
+
self,
|
37
|
+
location + "[#{key}]")
|
38
|
+
end
|
39
|
+
|
40
|
+
@method_hooks
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|