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.
- checksums.yaml +4 -4
- data/doc/Bauxite/Action.html +3 -3
- data/doc/Bauxite/ActionModule.html +4 -4
- data/doc/Bauxite/Application.html +1 -1
- data/doc/Bauxite/Context.html +184 -143
- data/doc/Bauxite/Errors/AssertionError.html +1 -1
- data/doc/Bauxite/Errors/FileNotFoundError.html +1 -1
- data/doc/Bauxite/Errors/FormatError.html +107 -0
- data/doc/Bauxite/Errors.html +1 -1
- data/doc/Bauxite/Loggers/CompositeLogger.html +1 -1
- data/doc/Bauxite/Loggers/EchoLogger.html +2 -1
- data/doc/Bauxite/Loggers/FileLogger.html +1 -1
- data/doc/Bauxite/Loggers/NullLogger.html +6 -6
- data/doc/Bauxite/Loggers/TerminalLogger.html +15 -10
- data/doc/Bauxite/Loggers/XtermLogger.html +1 -1
- data/doc/Bauxite/Loggers.html +1 -1
- data/doc/Bauxite/Parser.html +325 -0
- data/doc/Bauxite/ParserModule.html +203 -0
- data/doc/Bauxite/Selector.html +1 -1
- data/doc/Bauxite/SelectorModule.html +4 -4
- data/doc/Bauxite.html +1 -1
- data/doc/created.rid +42 -38
- data/doc/index.html +5 -1
- data/doc/js/search_index.js +1 -1
- data/doc/table_of_contents.html +77 -36
- data/lib/bauxite/actions/alias.rb +1 -1
- data/lib/bauxite/actions/debug.rb +2 -3
- data/lib/bauxite/actions/tryload.rb +1 -1
- data/lib/bauxite/application.rb +2 -10
- data/lib/bauxite/core/{Context.rb → context.rb} +117 -114
- data/lib/bauxite/core/{Errors.rb → errors.rb} +6 -0
- data/lib/bauxite/core/parser.rb +78 -0
- data/lib/bauxite/loggers/echo.rb +1 -0
- data/lib/bauxite/loggers/terminal.rb +5 -0
- data/lib/bauxite/parsers/csv.rb +49 -0
- data/lib/bauxite/parsers/default.rb +42 -0
- data/lib/bauxite/parsers/html.rb +76 -0
- data/lib/bauxite.rb +1 -1
- data/test/alias.bxt +3 -0
- data/test/parsers/page.html +7 -0
- data/test/parsers.bxt +2 -0
- data/test/parsers.csv +7 -0
- data/test/parsers.html +32 -0
- metadata +18 -7
- /data/lib/bauxite/core/{Action.rb → action.rb} +0 -0
- /data/lib/bauxite/core/{Logger.rb → logger.rb} +0 -0
- /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
|
207
|
-
#
|
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
|
-
#
|
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(
|
217
|
-
|
218
|
-
|
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
|
-
#
|
244
|
-
#
|
245
|
-
# See #parse_action for more details.
|
219
|
+
|
220
|
+
# Executes the specified +file+.
|
246
221
|
#
|
247
222
|
# For example:
|
248
|
-
# ctx.
|
249
|
-
# # =>
|
223
|
+
# ctx.exec_file('file')
|
224
|
+
# # => executes every action defined in 'file'
|
250
225
|
#
|
251
|
-
def
|
252
|
-
|
253
|
-
|
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.
|
@@ -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
|
data/lib/bauxite/loggers/echo.rb
CHANGED
@@ -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
data/test/alias.bxt
CHANGED
data/test/parsers.bxt
ADDED
data/test/parsers.csv
ADDED
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>
|