bauxite 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|