scoutui 2.0.0 → 2.0.1
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/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +154 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/scoutui_driver.rb +22 -0
- data/bin/setup +7 -0
- data/examples/ex1/commands.holidays.yml +45 -0
- data/examples/ex1/commands.yml +25 -0
- data/examples/ex1/test-example.sh +38 -0
- data/examples/ex1/test.config.json +16 -0
- data/examples/ex2/commands.yml +35 -0
- data/examples/ex2/page_model.json +28 -0
- data/examples/ex2/test-example.sh +39 -0
- data/examples/ex2/test.config.json +25 -0
- data/lib/scoutui.rb +8 -0
- data/lib/scoutui/appmodel/q_model.rb +82 -0
- data/lib/scoutui/base/assertions.rb +62 -0
- data/lib/scoutui/base/q_accounts.rb +52 -0
- data/lib/scoutui/base/q_applitools.rb +127 -0
- data/lib/scoutui/base/q_browser.rb +185 -0
- data/lib/scoutui/base/q_form.rb +261 -0
- data/lib/scoutui/base/test_scout.rb +120 -0
- data/lib/scoutui/base/test_settings.rb +109 -0
- data/lib/scoutui/base/user_vars.rb +108 -0
- data/lib/scoutui/base/visual_test_framework.rb +574 -0
- data/lib/scoutui/commands/click_object.rb +45 -0
- data/lib/scoutui/commands/command.rb +56 -0
- data/lib/scoutui/commands/commands.rb +133 -0
- data/lib/scoutui/commands/exists_alert.rb +54 -0
- data/lib/scoutui/commands/fill_form.rb +56 -0
- data/lib/scoutui/commands/jsalert/action_jsalert.rb +58 -0
- data/lib/scoutui/commands/mouse_over.rb +31 -0
- data/lib/scoutui/commands/pause.rb +26 -0
- data/lib/scoutui/commands/select_object.rb +54 -0
- data/lib/scoutui/commands/strategy.rb +202 -0
- data/lib/scoutui/commands/submit_form.rb +44 -0
- data/lib/scoutui/commands/type.rb +44 -0
- data/lib/scoutui/commands/update_url.rb +45 -0
- data/lib/scoutui/commands/utils.rb +128 -0
- data/lib/scoutui/commands/verify_element.rb +198 -0
- data/lib/scoutui/commands/verify_form.rb +26 -0
- data/lib/scoutui/eyes/eye_factory.rb +76 -0
- data/lib/scoutui/eyes/eye_scout.rb +239 -0
- data/lib/scoutui/logger/log_mgr.rb +105 -0
- data/lib/scoutui/navigator.rb +24 -0
- data/lib/scoutui/utils/utils.rb +352 -0
- data/lib/scoutui/version.rb +3 -0
- data/scoutui.gemspec +35 -0
- metadata +54 -2
- data/pkg/scoutui-2.0.0.gem +0 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require 'logging'
|
5
|
+
|
6
|
+
module Scoutui::Logger
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
class LogMgr
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
attr_accessor :root
|
14
|
+
attr_accessor :commands
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
|
18
|
+
@root = Logging.logger(STDOUT)
|
19
|
+
@root.level = :debug
|
20
|
+
|
21
|
+
Logging.appenders.stderr('Standard Error', :level => :error)
|
22
|
+
|
23
|
+
# Logging.appenders.file('Command File', :filename => 'command.log')
|
24
|
+
# Logging.logger['Commands'].appenders = 'Command File'
|
25
|
+
|
26
|
+
@asserts = Logging.logger['Assertions']
|
27
|
+
@asserts.add_appenders(
|
28
|
+
Logging.appenders.stdout
|
29
|
+
)
|
30
|
+
@asserts.add_appenders(
|
31
|
+
Logging.appenders.file("assertions.log")
|
32
|
+
)
|
33
|
+
|
34
|
+
@asserts.level = :debug
|
35
|
+
|
36
|
+
@commands = Logging.logger['Commands']
|
37
|
+
@commands.add_appenders(
|
38
|
+
Logging.appenders.stdout,
|
39
|
+
Logging.appenders.file('commands.log')
|
40
|
+
)
|
41
|
+
@commands.level = :debug
|
42
|
+
|
43
|
+
#Logging.logger.root.level = :warn
|
44
|
+
end
|
45
|
+
|
46
|
+
def asserts
|
47
|
+
@asserts
|
48
|
+
end
|
49
|
+
|
50
|
+
def command
|
51
|
+
@commands
|
52
|
+
end
|
53
|
+
def commands
|
54
|
+
@commands
|
55
|
+
end
|
56
|
+
|
57
|
+
def setLevel(_level)
|
58
|
+
@root.level = _level.to_sym
|
59
|
+
end
|
60
|
+
|
61
|
+
def warn(txt)
|
62
|
+
log('warn', txt)
|
63
|
+
end
|
64
|
+
|
65
|
+
def err(txt)
|
66
|
+
error(txt)
|
67
|
+
end
|
68
|
+
|
69
|
+
def error(txt)
|
70
|
+
log('error', txt)
|
71
|
+
end
|
72
|
+
|
73
|
+
def fatal(txt)
|
74
|
+
log('fatal', txt)
|
75
|
+
end
|
76
|
+
|
77
|
+
def info(txt)
|
78
|
+
log('info', txt)
|
79
|
+
end
|
80
|
+
|
81
|
+
def debug(txt)
|
82
|
+
log('debug', txt)
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def log(level, txt)
|
87
|
+
|
88
|
+
if level.match(/info/i)
|
89
|
+
@root.info txt
|
90
|
+
elsif level.match(/warn/i)
|
91
|
+
@root.warn txt
|
92
|
+
elsif level.match(/debug/i)
|
93
|
+
@root.debug txt
|
94
|
+
elsif level.match(/error/i)
|
95
|
+
@root.error txt
|
96
|
+
elsif level.match(/fatal/i)
|
97
|
+
@root.fatal txt
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'version'
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
class Scoutui::Navigator
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :test_options
|
9
|
+
|
10
|
+
attr_reader :app_name
|
11
|
+
attr_reader :test_name
|
12
|
+
attr_reader :viewport_size
|
13
|
+
attr_reader :driver
|
14
|
+
attr_reader :test_list
|
15
|
+
|
16
|
+
def initialize(opts={})
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,352 @@
|
|
1
|
+
|
2
|
+
require 'singleton'
|
3
|
+
require 'pp'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
module Scoutui::Utils
|
9
|
+
|
10
|
+
|
11
|
+
class TestUtils
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
attr_accessor :options
|
15
|
+
attr_accessor :app_model
|
16
|
+
attr_accessor :currentTest
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
|
20
|
+
@env_list={:accounts => 'SCOUTUI_ACCOUNTS', :browser => 'SCOUTUI_BROWSER', :applitools_api_key => 'APPLITOOLS_API_KEY'}
|
21
|
+
@options={}
|
22
|
+
@currentTest={:reqid => 'UI', :testcase => '00' }
|
23
|
+
|
24
|
+
[:accounts, :browser, :capabilities, :diffs_dir, :test_file, :host, :loc, :title, :viewport,
|
25
|
+
:userid, :password, :json_config_file, :page_model, :test_config, :debug].each do |o|
|
26
|
+
@options[o]=nil
|
27
|
+
end
|
28
|
+
|
29
|
+
@options[:role]=nil
|
30
|
+
@options[:sauce_name]='unnamed'
|
31
|
+
@options[:enable_eyes]=false
|
32
|
+
@options[:enable_sauce]=false
|
33
|
+
@options[:log_level]=:info # :debug, :info, :warn, :error, :fatal
|
34
|
+
@options[:match_level]='layout'
|
35
|
+
@options[:debug]=false
|
36
|
+
|
37
|
+
@app_model=nil
|
38
|
+
|
39
|
+
Scoutui::Base::UserVars.instance.set('eyes.viewport', '1024x768')
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def getReq()
|
44
|
+
@currentTest[:reqid]
|
45
|
+
end
|
46
|
+
|
47
|
+
def setReq(_r='UI')
|
48
|
+
@currentTest[:reqid]=_r
|
49
|
+
end
|
50
|
+
|
51
|
+
def loadModel(f=nil)
|
52
|
+
if f.nil?
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
@app_model = Scoutui::ApplicationModel::QModel.new(f)
|
58
|
+
@app_model.getAppModel()
|
59
|
+
rescue => ex
|
60
|
+
raise "ErrorLoad : #{f.to_s}"
|
61
|
+
end
|
62
|
+
|
63
|
+
@app_model
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def getForm(s)
|
68
|
+
_h = getPageElement(s)
|
69
|
+
Scoutui::Base::QForm.new(_h)
|
70
|
+
end
|
71
|
+
|
72
|
+
def getPageElement(s)
|
73
|
+
@app_model.getPageElement(s)
|
74
|
+
end
|
75
|
+
|
76
|
+
def parseCommandLine()
|
77
|
+
|
78
|
+
OptionParser.new do |opt|
|
79
|
+
opt.on('-c', '--config TESTFILE') { |o|
|
80
|
+
if !o.nil?
|
81
|
+
@options[:json_config_file]=o
|
82
|
+
|
83
|
+
jFile = File.read(@options[:json_config_file])
|
84
|
+
@options[:test_config]=jsonData=JSON.parse(jFile)
|
85
|
+
end
|
86
|
+
}
|
87
|
+
opt.on('--accounts [Account]') { |o| @options[:accounts] = o }
|
88
|
+
opt.on('-b', '--browser [TYPE]', [:chrome, :firefox, :ie, :safari, :phantomjs], "Select browser (chrome, ie, firefox, safari)") { |o| @options[:browser] = o }
|
89
|
+
opt.on('--capabilities CAP') { |o|
|
90
|
+
@options[:capabilities]=o
|
91
|
+
|
92
|
+
jFile = File.read(o)
|
93
|
+
@options[:capabilities]=jsonData=JSON.parse(jFile, :symbolize_names => true)
|
94
|
+
}
|
95
|
+
|
96
|
+
opt.on('--loglevel Level') { |o|
|
97
|
+
if o.match(/error/i)
|
98
|
+
@options[:log_level] = :error
|
99
|
+
elsif o.match(/fatal/i)
|
100
|
+
@options[:log_level] = :fatal
|
101
|
+
elsif o.match(/info/i)
|
102
|
+
@options[:log_level] = :info
|
103
|
+
elsif o.match(/warn/i)
|
104
|
+
@options[:log_level] = :warn
|
105
|
+
elsif o.match(/debug/i)
|
106
|
+
@options[:log_level] = :debug
|
107
|
+
end
|
108
|
+
|
109
|
+
Scoutui::Logger::LogMgr.instance.setLevel(@options[:log_level])
|
110
|
+
}
|
111
|
+
|
112
|
+
opt.on('--diffs Full Path') { |o|
|
113
|
+
@options[:diffs_dir] = o
|
114
|
+
}
|
115
|
+
|
116
|
+
opt.on('-d', '--debug', 'Enable debug') { |o|
|
117
|
+
@options[:debug] = true
|
118
|
+
@options[:log_level] = :debug
|
119
|
+
}
|
120
|
+
opt.on('--dut DUT') { |o| @options[:dut]=o }
|
121
|
+
opt.on('-h', '--host HOST') { |o| @options[:host] = o }
|
122
|
+
opt.on('-l', '--lang LOCAL') { |o| @options[:loc] = o }
|
123
|
+
opt.on('-k', '--key EyesLicense') { |o| options[:license_file] = o }
|
124
|
+
opt.on('-a', '--app AppName') { |o| @options[:app] = o }
|
125
|
+
opt.on('--match [LEVEL]', [:layout2, :layout, :strict, :exact, :content], "Select match level (layout, strict, exact, content)") { |o| @options[:match_level] = o }
|
126
|
+
|
127
|
+
opt.on("--pages a,b,c", Array, "List of page models") do |list|
|
128
|
+
Scoutui::Logger::LogMgr.instance.info __FILE__ + (__LINE__).to_s + " list => #{list}"
|
129
|
+
@options[:pages]=list
|
130
|
+
|
131
|
+
loadModel(@options[:pages])
|
132
|
+
end
|
133
|
+
|
134
|
+
opt.on('--pagemodel [PageModel]') { |o|
|
135
|
+
@options[:page_model] = o
|
136
|
+
loadModel(@options[:page_model].to_s)
|
137
|
+
}
|
138
|
+
opt.on('-t', '--title TITLE') { |o| @options[:title] = o }
|
139
|
+
|
140
|
+
opt.on('-u', '--user USER_ID') { |o|
|
141
|
+
@options[:userid] = o
|
142
|
+
Scoutui::Base::UserVars.instance.setVar(:user, @options[:userid].to_s)
|
143
|
+
}
|
144
|
+
opt.on('-p', '--password PASSWORD') { |o| @options[:password] = o }
|
145
|
+
opt.on('-e', '--eyes', "Toggle eyes") {
|
146
|
+
@options[:enable_eyes]=true
|
147
|
+
}
|
148
|
+
|
149
|
+
opt.on('--role ROLE') { |o| @options[:role]=o }
|
150
|
+
|
151
|
+
opt.on('-s', '--sauce', "Toggle SauceLabs") {
|
152
|
+
@options[:enable_sauce]=true
|
153
|
+
}
|
154
|
+
opt.on('--sauce_name NAME') { |o| @options[:sauce_name] = o }
|
155
|
+
opt.on('--viewport [resolution]') { |o| options[:viewport] = o }
|
156
|
+
end.parse!
|
157
|
+
|
158
|
+
if Scoutui::Utils::TestUtils.instance.isDebug?
|
159
|
+
Scoutui::Logger::LogMgr.instance.info __FILE__ + (__LINE__).to_s + " " + @options.to_s
|
160
|
+
Scoutui::Logger::LogMgr.instance.info "Test file => #{@options[:test_file]}"
|
161
|
+
Scoutui::Logger::LogMgr.instance.info "Host => #{@options[:host]}"
|
162
|
+
Scoutui::Logger::LogMgr.instance.info "Loc => #{@options[:loc]}"
|
163
|
+
Scoutui::Logger::LogMgr.instance.info "Title => #{@options[:title]}"
|
164
|
+
Scoutui::Logger::LogMgr.instance.info "Browser => #{@options[:browser]}"
|
165
|
+
Scoutui::Logger::LogMgr.instance.info "UserID => #{@options[:userid]}"
|
166
|
+
Scoutui::Logger::LogMgr.instance.info "Password => #{@options[:password]}"
|
167
|
+
Scoutui::Logger::LogMgr.instance.info "Eyes => #{@options[:enable_eyes]}"
|
168
|
+
Scoutui::Logger::LogMgr.instance.info "Test Cfg => #{@options[:json_config_file]}"
|
169
|
+
Scoutui::Logger::LogMgr.instance.info "Match Level => #{@options[:match_level]}"
|
170
|
+
Scoutui::Logger::LogMgr.instance.info "Accounts => #{@options[:accounts]}"
|
171
|
+
Scoutui::Logger::LogMgr.instance.info "Viewport => #{@options[:viewport]}"
|
172
|
+
Scoutui::Logger::LogMgr.instance.info "Viewport (Var) => #{Scoutui::Base::UserVars.instance.getViewPort().to_s}"
|
173
|
+
Scoutui::Logger::LogMgr.instance.info "PageModel file => #{@options[:page_model].to_s}"
|
174
|
+
end
|
175
|
+
|
176
|
+
@options
|
177
|
+
end
|
178
|
+
|
179
|
+
def getCapabilities()
|
180
|
+
@options[:capabilities]
|
181
|
+
end
|
182
|
+
|
183
|
+
def getHost()
|
184
|
+
@options[:host].to_s
|
185
|
+
end
|
186
|
+
|
187
|
+
def setDebug(b)
|
188
|
+
@options[:debug]=b
|
189
|
+
end
|
190
|
+
|
191
|
+
def isDebug?
|
192
|
+
@options[:debug]
|
193
|
+
end
|
194
|
+
|
195
|
+
def eyesEnabled?
|
196
|
+
@options[:enable_eyes]
|
197
|
+
end
|
198
|
+
|
199
|
+
def sauceEnabled?
|
200
|
+
@options[:enable_sauce]
|
201
|
+
end
|
202
|
+
|
203
|
+
def getLicenseFile()
|
204
|
+
@options[:license_file].to_s
|
205
|
+
end
|
206
|
+
|
207
|
+
def getRole()
|
208
|
+
@options[:role]
|
209
|
+
end
|
210
|
+
|
211
|
+
def getSauceName()
|
212
|
+
@options[:sauce_name].to_s
|
213
|
+
end
|
214
|
+
|
215
|
+
def getBrowser()
|
216
|
+
getBrowserType()
|
217
|
+
end
|
218
|
+
def getBrowserType()
|
219
|
+
@options[:browser]
|
220
|
+
end
|
221
|
+
|
222
|
+
def hasTestConfig?
|
223
|
+
!@options[:json_config_file].nil?
|
224
|
+
end
|
225
|
+
|
226
|
+
def testConfigFile()
|
227
|
+
@options[:json_config_file]
|
228
|
+
end
|
229
|
+
|
230
|
+
# Returns JSON file contents/format
|
231
|
+
def getTestSettings()
|
232
|
+
|
233
|
+
Scoutui::Logger::LogMgr.instance.setLevel(@options[:log_level])
|
234
|
+
|
235
|
+
[:accounts, :browser, :dut, :host, :userid, :password].each do |k|
|
236
|
+
|
237
|
+
Scoutui::Logger::LogMgr.instance.info __FILE__ + (__LINE__).to_s + " opt[test_config].has_key(#{k.to_s}) => #{@options[:test_config].has_key?(k.to_s)}" if Scoutui::Utils::TestUtils.instance.isDebug?
|
238
|
+
|
239
|
+
Scoutui::Logger::LogMgr.instance.info __FILE__ + (__LINE__).to_s + " options[#{k}] : #{@options[k].to_s}" if Scoutui::Utils::TestUtils.instance.isDebug?
|
240
|
+
if @options.has_key?(k) && !@options[k].nil?
|
241
|
+
Scoutui::Base::UserVars.instance.set(k, @options[k].to_s)
|
242
|
+
elsif @options[:test_config].has_key?(k.to_s)
|
243
|
+
|
244
|
+
Scoutui::Logger::LogMgr.instance.info __FILE__ + (__LINE__).to_s + " opts[#{k}].nil => #{@options[k].nil?}" if Scoutui::Utils::TestUtils.instance.isDebug?
|
245
|
+
# Ensure commnand line takes precedence
|
246
|
+
if !@options[k].nil?
|
247
|
+
Scoutui::Logger::LogMgr.instance.info __FILE__ + (__LINE__).to_s + " opt[#{k.to_s} => #{@options[k].to_s}" if Scoutui::Utils::TestUtils.instance.isDebug?
|
248
|
+
Scoutui::Base::UserVars.instance.set(k, @options[k].to_s)
|
249
|
+
else
|
250
|
+
Scoutui::Base::UserVars.instance.set(k, @options[:test_config][k.to_s].to_s)
|
251
|
+
end
|
252
|
+
|
253
|
+
elsif @env_list.has_key?(k)
|
254
|
+
# If an ENV is available, use it.
|
255
|
+
Scoutui::Logger::LogMgr.instance.debug __FILE__ + (__LINE__).to_s + " #{k} => ENV(#{@env_list[k]}) = #{ENV[@env_list[k].to_s]}" if Scoutui::Utils::TestUtils.instance.isDebug?
|
256
|
+
Scoutui::Base::UserVars.instance.set(k, ENV[@env_list[k].to_s])
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
Scoutui::Logger::LogMgr.instance.debug __FILE__ + (__LINE__).to_s + " test_config => #{@options[:test_config]}" if Scoutui::Utils::TestUtils.instance.isDebug?
|
261
|
+
|
262
|
+
if @options[:test_config].has_key?('dut') && @options.has_key?(:dut)
|
263
|
+
@options[:test_config]['dut']=@options[:dut]
|
264
|
+
end
|
265
|
+
|
266
|
+
# Applitools Eyes settings
|
267
|
+
if @options[:test_config].has_key?('eyes')
|
268
|
+
|
269
|
+
['match_level', 'title', 'app', 'viewport'].each do |k|
|
270
|
+
|
271
|
+
_v=nil
|
272
|
+
|
273
|
+
if @options[:test_config]['eyes'].has_key?(k)
|
274
|
+
_v=@options[:test_config]['eyes'][k].to_s
|
275
|
+
end
|
276
|
+
|
277
|
+
if !@options[k.to_sym].nil?
|
278
|
+
_v=@options[k.to_sym].to_s
|
279
|
+
end
|
280
|
+
|
281
|
+
if Scoutui::Utils::TestUtils.instance.isDebug?
|
282
|
+
Scoutui::Logger::LogMgr.instance.debug __FILE__ + (__LINE__).to_s + " #{k} => #{_v}"
|
283
|
+
end
|
284
|
+
|
285
|
+
Scoutui::Base::UserVars.instance.set('eyes.' + k, _v) if !_v.nil?
|
286
|
+
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
|
291
|
+
|
292
|
+
@options[:test_config]
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
def getTestConfig()
|
297
|
+
@options[:test_config]
|
298
|
+
end
|
299
|
+
|
300
|
+
def match_level()
|
301
|
+
@options[:match_level]
|
302
|
+
end
|
303
|
+
|
304
|
+
def getDiffDir()
|
305
|
+
@options[:diffs_dir]
|
306
|
+
end
|
307
|
+
|
308
|
+
def getUserId()
|
309
|
+
@options[:userid]
|
310
|
+
end
|
311
|
+
|
312
|
+
def getUser()
|
313
|
+
getUserId()
|
314
|
+
end
|
315
|
+
|
316
|
+
def getPassword()
|
317
|
+
@options[:password]
|
318
|
+
end
|
319
|
+
|
320
|
+
def testFile()
|
321
|
+
@options[:test_file]
|
322
|
+
end
|
323
|
+
|
324
|
+
def host()
|
325
|
+
@options[:host]
|
326
|
+
end
|
327
|
+
|
328
|
+
def loc()
|
329
|
+
@options[:loc]
|
330
|
+
end
|
331
|
+
def localization()
|
332
|
+
loc()
|
333
|
+
end
|
334
|
+
|
335
|
+
def appName()
|
336
|
+
@options[:app].to_s
|
337
|
+
end
|
338
|
+
|
339
|
+
def title()
|
340
|
+
@options[:title]
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
|
347
|
+
|
348
|
+
|
349
|
+
|
350
|
+
|
351
|
+
|
352
|
+
end
|