bauxite 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/doc/Bauxite/Action.html +3 -3
  3. data/doc/Bauxite/ActionModule.html +4 -4
  4. data/doc/Bauxite/Application.html +1 -1
  5. data/doc/Bauxite/Context.html +184 -143
  6. data/doc/Bauxite/Errors/AssertionError.html +1 -1
  7. data/doc/Bauxite/Errors/FileNotFoundError.html +1 -1
  8. data/doc/Bauxite/Errors/FormatError.html +107 -0
  9. data/doc/Bauxite/Errors.html +1 -1
  10. data/doc/Bauxite/Loggers/CompositeLogger.html +1 -1
  11. data/doc/Bauxite/Loggers/EchoLogger.html +2 -1
  12. data/doc/Bauxite/Loggers/FileLogger.html +1 -1
  13. data/doc/Bauxite/Loggers/NullLogger.html +6 -6
  14. data/doc/Bauxite/Loggers/TerminalLogger.html +15 -10
  15. data/doc/Bauxite/Loggers/XtermLogger.html +1 -1
  16. data/doc/Bauxite/Loggers.html +1 -1
  17. data/doc/Bauxite/Parser.html +325 -0
  18. data/doc/Bauxite/ParserModule.html +203 -0
  19. data/doc/Bauxite/Selector.html +1 -1
  20. data/doc/Bauxite/SelectorModule.html +4 -4
  21. data/doc/Bauxite.html +1 -1
  22. data/doc/created.rid +42 -38
  23. data/doc/index.html +5 -1
  24. data/doc/js/search_index.js +1 -1
  25. data/doc/table_of_contents.html +77 -36
  26. data/lib/bauxite/actions/alias.rb +1 -1
  27. data/lib/bauxite/actions/debug.rb +2 -3
  28. data/lib/bauxite/actions/tryload.rb +1 -1
  29. data/lib/bauxite/application.rb +2 -10
  30. data/lib/bauxite/core/{Context.rb → context.rb} +117 -114
  31. data/lib/bauxite/core/{Errors.rb → errors.rb} +6 -0
  32. data/lib/bauxite/core/parser.rb +78 -0
  33. data/lib/bauxite/loggers/echo.rb +1 -0
  34. data/lib/bauxite/loggers/terminal.rb +5 -0
  35. data/lib/bauxite/parsers/csv.rb +49 -0
  36. data/lib/bauxite/parsers/default.rb +42 -0
  37. data/lib/bauxite/parsers/html.rb +76 -0
  38. data/lib/bauxite.rb +1 -1
  39. data/test/alias.bxt +3 -0
  40. data/test/parsers/page.html +7 -0
  41. data/test/parsers.bxt +2 -0
  42. data/test/parsers.csv +7 -0
  43. data/test/parsers.html +32 -0
  44. metadata +18 -7
  45. /data/lib/bauxite/core/{Action.rb → action.rb} +0 -0
  46. /data/lib/bauxite/core/{Logger.rb → logger.rb} +0 -0
  47. /data/lib/bauxite/core/{Selector.rb → selector.rb} +0 -0
@@ -31,6 +31,7 @@ lambda do
31
31
  Dir[File.join(dir, '..', 'actions' , '*.rb')].each { |file| require file }
32
32
  Dir[File.join(dir, '..', 'selectors', '*.rb')].each { |file| require file }
33
33
  Dir[File.join(dir, '..', 'loggers' , '*.rb')].each { |file| require file }
34
+ Dir[File.join(dir, '..', 'parsers' , '*.rb')].each { |file| require file }
34
35
  end.call
35
36
 
36
37
  module Bauxite
@@ -50,9 +51,6 @@ module Bauxite
50
51
  # Context variables.
51
52
  attr_accessor :variables
52
53
 
53
- # Action aliases.
54
- attr_accessor :aliases
55
-
56
54
  # Test containers.
57
55
  attr_accessor :tests
58
56
 
@@ -82,6 +80,8 @@ module Bauxite
82
80
  handle_errors do
83
81
  @logger = Context::load_logger(options[:logger], options[:logger_opt])
84
82
  end
83
+
84
+ @parser = Parser.new(self)
85
85
  end
86
86
 
87
87
  # Starts the test engine and executes the actions specified. If no action
@@ -203,88 +203,32 @@ module Bauxite
203
203
  # :section: Advanced Helpers
204
204
  # ======================================================================= #
205
205
 
206
- # Executes the specified action handling errors, logging and debug history.
207
- # Actions can be obtained by calling #parse_action.
206
+ # Executes the specified action string handling errors, logging and debug
207
+ # history.
208
208
  #
209
209
  # If +log+ is +true+, log the action execution (default behavior).
210
210
  #
211
211
  # For example:
212
- # action = ctx.parse_action('open "http://www.ruby-lang.org"')
213
- # ctx.exec_action action
212
+ # ctx.exec_action 'open "http://www.ruby-lang.org"'
214
213
  # # => navigates to www.ruby-lang.org
215
214
  #
216
- def exec_action(action, log = true)
217
- if (action.is_a? String)
218
- action = { :text => action, :file => '<unknown>', :line => 0 }
219
- end
220
-
221
- ret = handle_errors(true) do
222
-
223
- action = _load_action(action)
224
-
225
- # Inject built-in variables
226
- file = action.file
227
- dir = (File.exists? file) ? File.dirname(file) : Dir.pwd
228
- @variables['__FILE__'] = file
229
- @variables['__DIR__'] = File.absolute_path(dir)
230
-
231
- if log
232
- @logger.log_cmd(action) do
233
- Readline::HISTORY << action.text
234
- action.execute
235
- end
236
- else
237
- action.execute
238
- end
239
- end
240
- ret.call if ret.respond_to? :call # delayed actions (after log_cmd)
215
+ def exec_action(text, log = true, file = '<unknown>', line = 0)
216
+ data = Context::parse_action_default(text, file, line)
217
+ _exec_parsed_action(data[:action], data[:args], text, log, file, line)
241
218
  end
242
-
243
- # Parses the specified text into a test action array.
244
- #
245
- # See #parse_action for more details.
219
+
220
+ # Executes the specified +file+.
246
221
  #
247
222
  # For example:
248
- # ctx.parse_file('file')
249
- # # => [ { :cmd => 'echo', ... } ]
223
+ # ctx.exec_file('file')
224
+ # # => executes every action defined in 'file'
250
225
  #
251
- def parse_file(file)
252
- if (file == 'stdin')
253
- _parse_file($stdin, file)
254
- else
255
- File.open(file) { |io| _parse_file(io, file) }
226
+ def exec_file(file)
227
+ @parser.parse(file) do |action, args, text, file, line|
228
+ _exec_parsed_action(action, args, text, true, file, line)
256
229
  end
257
230
  end
258
231
 
259
- # Parses a line of action text into an array. The input +line+ should be a
260
- # space-separated list of values, surrounded by optional quotes (").
261
- #
262
- # The first element in +line+ will be interpreted as an action name. Valid
263
- # action names are retuned by ::actions.
264
- #
265
- # The other elements in +line+ will be interpreted as action arguments.
266
- #
267
- # For example:
268
- # Context::parse_args('echo "Hello World!"')
269
- # # => ' ["echo", "Hello World!"]
270
- #
271
- def self.parse_args(line)
272
- # col_sep must be a regex because String.split has a special case for
273
- # a single space char (' ') that produced unexpected results (i.e.
274
- # if line is '"a b"' the resulting array contains ["a b"]).
275
- #
276
- # ...but...
277
- #
278
- # CSV expects col_sep to be a string so we need to work some dark magic
279
- # here. Basically we proxy the StringIO received by CSV to returns
280
- # strings for which the split method does not fold the whitespaces.
281
- #
282
- return [] if line.strip == ''
283
- CSV.new(StringIOProxy.new(line), { :col_sep => ' ' })
284
- .shift
285
- .select { |a| a != nil }
286
- end
287
-
288
232
  # Executes the +block+ inside a rescue block applying standard criteria of
289
233
  # error handling.
290
234
  #
@@ -381,6 +325,48 @@ module Bauxite
381
325
  Loggers.const_get(class_name).new(options)
382
326
  end
383
327
 
328
+ # Adds an alias named +name+ to the specified +action+ with the
329
+ # arguments specified in +args+.
330
+ #
331
+ def add_alias(name, action, args)
332
+ @aliases[name] = { :action => action, :args => args }
333
+ end
334
+
335
+ # Default action parsing strategy.
336
+ #
337
+ def self.parse_action_default(text, file = '<unknown>', line = 0)
338
+ data = text.split(' ', 2)
339
+ begin
340
+ args_text = data[1] ? data[1].strip : ''
341
+ args = []
342
+
343
+ unless args_text == ''
344
+ # col_sep must be a regex because String.split has a
345
+ # special case for a single space char (' ') that produced
346
+ # unexpected results (i.e. if line is '"a b"' the
347
+ # resulting array contains ["a b"]).
348
+ #
349
+ # ...but...
350
+ #
351
+ # CSV expects col_sep to be a string so we need to work
352
+ # some dark magic here. Basically we proxy the StringIO
353
+ # received by CSV to returns strings for which the split
354
+ # method does not fold the whitespaces.
355
+ #
356
+ args = CSV.new(StringIOProxy.new(args_text), { :col_sep => ' ' })
357
+ .shift
358
+ .select { |a| a != nil } || []
359
+ end
360
+
361
+ {
362
+ :action => data[0].strip.downcase,
363
+ :args => args
364
+ }
365
+ rescue StandardError => e
366
+ raise "#{file} (line #{line+1}): #{e.message}"
367
+ end
368
+ end
369
+
384
370
  # ======================================================================= #
385
371
  # :section: Metadata
386
372
  # ======================================================================= #
@@ -434,6 +420,18 @@ module Bauxite
434
420
  Loggers.constants.map { |l| l.to_s.downcase.sub(/logger$/, '') }
435
421
  end
436
422
 
423
+ # Returns an array with the names of every parser available.
424
+ #
425
+ # For example:
426
+ # Context::parsers
427
+ # # => [ "default", "html", ... ]
428
+ #
429
+ def self.parsers
430
+ (Parser.public_instance_methods(false) \
431
+ - ParserModule.public_instance_methods(false))
432
+ .map { |p| p.to_s }
433
+ end
434
+
437
435
  # Returns the maximum size in characters of an action name.
438
436
  #
439
437
  # This method is useful to pretty print lists of actions
@@ -505,47 +503,6 @@ module Bauxite
505
503
  @driver = Selenium::WebDriver.for(@driver_name, @options[:driver_opt])
506
504
  @driver.manage.timeouts.implicit_wait = 1
507
505
  end
508
-
509
- def _load_action(action)
510
- text = action[:text]
511
- file = action[:file]
512
- line = action[:line]
513
-
514
- data = text.split(' ', 2)
515
- cmd = data[0].strip.downcase
516
- args = data[1] ? data[1].strip : ''
517
-
518
- begin
519
- args = Context::parse_args(args) || []
520
- rescue StandardError => e
521
- raise "#{file} (line #{line+1}): #{e.message}"
522
- end
523
-
524
- alias_cmd = @aliases[cmd]
525
- return Action.new(self, cmd, args, text, file, line) unless alias_cmd
526
-
527
- action[:text] = args.each_with_index.inject(alias_cmd) do |ret,vi|
528
- # expand ${1} to args[0], ${2} to args[1], etc.
529
- ret.gsub("${#{vi[1]+1}}", vi[0])
530
- end.gsub(/\$\{(\d+)\*(q)?\}/) do |match|
531
- # expand ${4*} to "#{args[4]} #{args[5]} ..."
532
- # expand ${4*q} to "\"#{args[4]}\" \"#{args[5]}\" ..."
533
- a = args[$1..-1]
534
- a = a.map { |arg| '"'+arg.gsub('"', '""')+'"' } if $2 == 'q'
535
- a.join(' ')
536
- end.gsub(/\$\{\d+\}/, '') # remove unexpanded ${1}, etc.
537
-
538
- _load_action(action)
539
- end
540
-
541
- def _parse_file(io, file)
542
- io.each_line.each_with_index.map do |text,line|
543
- text = text.sub(/\r?\n$/, '')
544
- next nil if text =~ /^\s*(#|$)/
545
- exec_action({ :text => text, :file => file, :line => line })
546
- end
547
- .select { |item| item != nil }
548
- end
549
506
 
550
507
  def _load_extensions(dirs)
551
508
  dirs.each do |d|
@@ -554,7 +511,53 @@ module Bauxite
554
511
  Dir[File.join(d, '**', '*.rb')].each { |file| require file }
555
512
  end
556
513
  end
557
-
514
+
515
+ def _exec_parsed_action(action, args, text, log, file, line)
516
+ ret = handle_errors(true) do
517
+
518
+ while (alias_action = @aliases[action])
519
+ action = alias_action[:action]
520
+ args = alias_action[:args].map do |a|
521
+ a.gsub(/\$\{(\d+)(\*q?)?\}/) do |match|
522
+ # expand ${1} to args[0], ${2} to args[1], etc.
523
+ # expand ${4*} to "#{args[4]} #{args[5]} ..."
524
+ # expand ${4*q} to "\"#{args[4]}\" \"#{args[5]}\" ..."
525
+ idx = $1.to_i-1
526
+ if $2 == nil
527
+ args[idx] || ''
528
+ else
529
+ range = args[idx..-1]
530
+ range = range.map { |arg| '"'+arg.gsub('"', '""')+'"' } if $2 == '*q'
531
+ range.join(' ')
532
+ end
533
+ end
534
+ end
535
+ end
536
+
537
+ text = ([action] + args.map { |a| '"'+a.gsub('"', '""')+'"' }).join(' ') unless text
538
+
539
+ action = Action.new(self, action, args, text, file, line)
540
+
541
+ # Inject built-in variables
542
+ file = action.file
543
+ dir = (File.exists? file) ? File.dirname(file) : Dir.pwd
544
+ @variables['__FILE__'] = file
545
+ @variables['__DIR__'] = File.absolute_path(dir)
546
+
547
+ if log
548
+ @logger.log_cmd(action) do
549
+ Readline::HISTORY << action.text
550
+ action.execute
551
+ end
552
+ else
553
+ action.execute
554
+ end
555
+ end
556
+ handle_errors(true) do
557
+ ret.call if ret.respond_to? :call # delayed actions (after log_cmd)
558
+ end
559
+ end
560
+
558
561
  # ======================================================================= #
559
562
  # Hacks required to overcome the String#split(' ') behavior of folding the
560
563
  # space characters, coupled with CSV not supporting a regex as :col_sep.
@@ -32,5 +32,11 @@ module Bauxite
32
32
  #
33
33
  class FileNotFoundError < StandardError
34
34
  end
35
+
36
+ # Error raised when trying to process an invalid file format.
37
+ #
38
+ class FormatError < StandardError
39
+ end
40
+
35
41
  end
36
42
  end
@@ -0,0 +1,78 @@
1
+ #--
2
+ # Copyright (c) 2014 Patricio Zavolinsky
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ # SOFTWARE.
21
+ #++
22
+
23
+ module Bauxite
24
+ # Parser common state and behavior.
25
+ module ParserModule
26
+ # Constructs a new test parser instance.
27
+ def initialize(ctx)
28
+ @ctx = ctx
29
+ end
30
+
31
+ # Parse +file+ and yield the resulting actions.
32
+ def parse(file)
33
+ actions = nil
34
+ Context::parsers.any? { |p| actions = send(p, file) }
35
+
36
+ unless actions
37
+ raise Errors::FormatError, "Invalid format in '#{file}'. None of the available parsers can understand this file format."
38
+ end
39
+
40
+ actions.each do |action, args, text, line|
41
+ yield action.strip.downcase, args, text, file, line
42
+ end
43
+ end
44
+ end
45
+
46
+ # Parser class.
47
+ #
48
+ # Parser represent different strategies for reading input files into lists
49
+ # of Bauxite actions.
50
+ #
51
+ # Each custom parser is defined in a separate file in the 'parsers/'
52
+ # directory. These files should avoid adding public methods other than
53
+ # the parser method itself. Also, no +attr_accessors+ should be added.
54
+ #
55
+ # Parser methods can use the +ctx+ attribute to refer to the current test
56
+ # Context.
57
+ # Parser methods receive a single action hash argument including <tt>:file</tt>
58
+ # and return an array of hashes or nil if the parser can't handle the file.
59
+ #
60
+ # For example:
61
+ # # === parsers/my_parser.rb ======= #
62
+ # class Parser
63
+ # # :category: Parser Methods
64
+ # def my_parser(action)
65
+ # # open and read file
66
+ # [
67
+ # { :cmd => 'echo', :args => [ 'hello world' ] },
68
+ # { :cmd => 'write',:args => [ 'id=username', 'jdoe' ] },
69
+ # { :cmd => 'write',:args => [ 'id=password', 'hello world!' ] }
70
+ # ]
71
+ # end
72
+ # end
73
+ # # === end parsers/my_parser.rb === #
74
+ #
75
+ class Parser
76
+ include ParserModule
77
+ end
78
+ end
@@ -31,6 +31,7 @@ class Bauxite::Loggers::EchoLogger < Bauxite::Loggers::NullLogger
31
31
  # Echoes the raw action text.
32
32
  def log_cmd(action)
33
33
  puts action.text
34
+ $stdout.flush
34
35
  yield
35
36
  end
36
37
  end
@@ -44,6 +44,8 @@ class Bauxite::Loggers::TerminalLogger < Bauxite::Loggers::NullLogger
44
44
  s = action.args(true).join(' ')
45
45
  s = s[0...max_args_size-3]+'...' if s.size > max_args_size
46
46
  print s.ljust(max_args_size)
47
+ $stdout.flush
48
+
47
49
  _save_cursor
48
50
  color = :green
49
51
  text = 'OK'
@@ -54,10 +56,12 @@ class Bauxite::Loggers::TerminalLogger < Bauxite::Loggers::NullLogger
54
56
  end
55
57
  _restore_cursor
56
58
  puts " #{_block(color, text, 5)}"
59
+ $stdout.flush
57
60
  ret
58
61
  rescue
59
62
  _restore_cursor
60
63
  puts " #{_block(:red, 'ERROR', 5)}"
64
+ $stdout.flush
61
65
  raise
62
66
  end
63
67
 
@@ -70,6 +74,7 @@ class Bauxite::Loggers::TerminalLogger < Bauxite::Loggers::NullLogger
70
74
  def progress(value)
71
75
  if _restore_cursor
72
76
  print " #{_block(:gray, value.to_s, 5)}"
77
+ $stdout.flush
73
78
  end
74
79
  end
75
80
 
@@ -0,0 +1,49 @@
1
+ #--
2
+ # Copyright (c) 2014 Patricio Zavolinsky
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ # SOFTWARE.
21
+ #++
22
+
23
+ class Bauxite::Parser
24
+ # Load CSV files.
25
+ #
26
+ # :category: Parser Methods
27
+ def csv(file)
28
+ return nil unless file.downcase.end_with? '.csv'
29
+
30
+ File.open(file) do |f|
31
+ f.read.each_line.each_with_index.map do |text,line|
32
+ text = text.sub(/\r?\n$/, '')
33
+ next nil if text =~ /^\s*(#|$)/
34
+ begin
35
+ data = CSV.parse_line(text)
36
+ [ data[0], data[1..-1].map { |a| a.strip }, nil, line ]
37
+ rescue StandardError => e
38
+ raise "#{file} (line #{line+1}): #{e.message}"
39
+ end
40
+ end
41
+ end.select { |item| item != nil }
42
+ end
43
+
44
+ private
45
+ def _selenium_ide_html_parse_selector(selector)
46
+ selector = 'xpath='+selector if selector[0..1] == '//'
47
+ selector
48
+ end
49
+ end
@@ -0,0 +1,42 @@
1
+ #--
2
+ # Copyright (c) 2014 Patricio Zavolinsky
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ # SOFTWARE.
21
+ #++
22
+
23
+ class Bauxite::Parser
24
+ # Load default Bauxite files.
25
+ #
26
+ # :category: Parser Methods
27
+ def default(file, io = nil)
28
+ return nil unless file == 'stdin' or file.match(/\.[tb]xt(\..*)?$/)
29
+
30
+ return default(file, $stdin) if file == 'stdin' and not io
31
+
32
+ return File.open(file) { |io| default(file, io) } unless io
33
+
34
+ io.each_line.each_with_index.map do |text,line|
35
+ text = text.sub(/\r?\n$/, '')
36
+ next nil if text =~ /^\s*(#|$)/
37
+ data = Bauxite::Context::parse_action_default(text, file, line)
38
+ [ data[:action], data[:args], text, line ]
39
+ end
40
+ .select { |item| item != nil }
41
+ end
42
+ end
@@ -0,0 +1,76 @@
1
+ #--
2
+ # Copyright (c) 2014 Patricio Zavolinsky
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ # SOFTWARE.
21
+ #++
22
+
23
+ class Bauxite::Parser
24
+ # Load Selenium IDE HTML files.
25
+ #
26
+ # :category: Parser Methods
27
+ def selenium_ide_html(file)
28
+ return nil unless file.downcase.end_with? '.html'
29
+
30
+ File.open(file) do |f|
31
+ content = f.read
32
+
33
+ data = content.gsub(/[\n\r]/, '')
34
+ selenium_base = data.sub(/.*rel="selenium.base" href="([^"]*)".*/, '\1')
35
+ base_ends_in_slash = (selenium_base[-1] == '/')
36
+
37
+ data
38
+ .gsub('<tr>', "\naction=")
39
+ .gsub('</tr>', "\n")
40
+ .each_line.grep(/^action=/)
41
+ .map { |line| line.match(/^action=\s*<td>([^<]*)<\/td>\s*<td>([^<]*)<\/td>\s*<td>([^<]*)<\/td>.*$/) }
42
+ .select { |match| match }
43
+ .map { |match| match.captures }
44
+ .map do |action|
45
+ case action[0]
46
+ when 'open'
47
+ url = action[1]
48
+ url = url[1..-1] if url[0] == '/' and base_ends_in_slash # remove leading '/'
49
+ action[1] = selenium_base + url
50
+ when 'type'
51
+ action[0] = 'write'
52
+ action[1] = _selenium_ide_html_parse_selector(action[1])
53
+ when 'verifyTextPresent'
54
+ action[0] = 'source'
55
+ when 'clickAndWait', 'click'
56
+ action[0] = 'click'
57
+ action[1] = _selenium_ide_html_parse_selector(action[1])
58
+ when 'waitForPageToLoad'
59
+ action[0] = 'wait'
60
+ action[1] = (action[1].to_i / 1000).to_s
61
+ when 'assertValue'
62
+ action[0] = 'assert'
63
+ action[1] = _selenium_ide_html_parse_selector(action[1])
64
+ end
65
+ action = action.select { |a| a != '' }
66
+ [ action[0], action[1..-1], nil, 0 ]
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+ def _selenium_ide_html_parse_selector(selector)
73
+ selector = 'xpath='+selector if selector[0..1] == '//'
74
+ selector
75
+ end
76
+ end
data/lib/bauxite.rb CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  #--
24
24
  module Bauxite
25
- VERSION = "0.3.1"
25
+ VERSION = "0.4.0"
26
26
  end
27
27
  #++
28
28
 
data/test/alias.bxt CHANGED
@@ -4,3 +4,6 @@ hey john
4
4
  alias set_hi set hi "hi ${1}"
5
5
  set_hi dude!
6
6
  assertv "^hi dude!$" "${hi}"
7
+
8
+ alias merge_hi echo "${1*}"
9
+ merge_hi how are you doing?
@@ -0,0 +1,7 @@
1
+ <html>
2
+ <body>
3
+ <div class="parent">
4
+ <input id="random_prefix_by_id" class="by_class" by_attr="attr_value">
5
+ </div>
6
+ </body>
7
+ </html>
data/test/parsers.bxt ADDED
@@ -0,0 +1,2 @@
1
+ load "parsers.csv"
2
+ load "parsers.html"
data/test/parsers.csv ADDED
@@ -0,0 +1,7 @@
1
+ open,"file://${__DIR__}/parsers/page.html"
2
+ write,by_id,hello
3
+ assert,"css=.parent input",hello
4
+ assert,"attr=by_attr:attr_value",hello
5
+ assert,"class=by_class",hello
6
+ assert,"tag_name=input",hello
7
+
data/test/parsers.html ADDED
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head profile="http://selenium-ide.openqa.org/profiles/test-case">
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6
+ <link rel="selenium.base" href="file://${__DIR__}/parsers" />
7
+ <title>parsers</title>
8
+ </head>
9
+ <body>
10
+ <table cellpadding="1" cellspacing="1" border="1">
11
+ <thead>
12
+ <tr><td rowspan="1" colspan="3">parsers</td></tr>
13
+ </thead><tbody>
14
+ <!--Manually changed Base URL to use a relative page url.-->
15
+ <tr>
16
+ <td>open</td>
17
+ <td>/page.html</td>
18
+ <td></td>
19
+ </tr>
20
+ <tr>
21
+ <td>type</td>
22
+ <td>id=random_prefix_by_id</td>
23
+ <td>hello</td>
24
+ </tr>
25
+ <tr>
26
+ <td>assertValue</td>
27
+ <td>id=random_prefix_by_id</td>
28
+ <td>hello</td>
29
+ </tr>
30
+ </tbody></table>
31
+ </body>
32
+ </html>