bauxite 0.3.1 → 0.4.0

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.
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>