Class: Bauxite::Context
- Inherits:
-
Object
- Object
- Bauxite::Context
- Defined in:
- lib/bauxite/core/context.rb
Overview
to change the test behavior by changing the built-in variables.
Defined Under Namespace
Classes: StringIOProxy, StringProxy
Instance Attribute Summary collapse
-
#logger ⇒ Object
readonly
Logger instance.
-
#options ⇒ Object
readonly
Test options.
-
#tests ⇒ Object
Test containers.
-
#variables ⇒ Object
Context variables.
Class Method Summary collapse
-
.action_args(action) ⇒ Object
Returns an array with the names of the arguments of the specified action.
-
.actions ⇒ Object
Returns an array with the names of every action available.
-
.load_logger(loggers, options) ⇒ Object
Constructs a Logger instance using
nameas a hint for the logger type. -
.loggers ⇒ Object
Returns an array with the names of every logger available.
-
.max_action_name_size ⇒ Object
Returns the maximum size in characters of an action name.
-
.parse_action_default(text, file = '<unknown>', line = 0) ⇒ Object
Default action parsing strategy.
-
.parsers ⇒ Object
Returns an array with the names of every parser available.
-
.selectors(include_standard_selectors = true) ⇒ Object
Returns an array with the names of every selector available.
-
.wait ⇒ Object
Prompts the user to press ENTER before resuming execution.
Instance Method Summary collapse
-
#add_alias(name, action, args) ⇒ Object
Adds an alias named
nameto the specifiedactionwith the arguments specified inargs. -
#debug ⇒ Object
Breaks into the debug console.
-
#driver ⇒ Object
Test engine driver instance (Selenium WebDriver).
-
#exec_action(text) ⇒ Object
Executes the specified action string handling errors, logging and debug history.
-
#exec_action_object(action) ⇒ Object
Executes the specified action object injecting built-in variables.
-
#exec_file(file) ⇒ Object
Executes the specified
file. -
#exec_parsed_action(action, args, log = true, text = nil) ⇒ Object
Executes the specified action handling errors, logging and debug history.
-
#expand(s) ⇒ Object
Recursively replaces occurencies of variable expansions in
swith the corresponding variable value. -
#find(selector, &block) ⇒ Object
Finds an element by
selector. -
#get_action(action, args, text = nil) ⇒ Object
Returns an executable Action object constructed from the specified arguments resolving action aliases.
-
#get_value(element) ⇒ Object
Returns the value of the specified
element. -
#initialize(options) ⇒ Context
constructor
Constructs a new test context instance.
-
#output_path(path) ⇒ Object
Returns the output path for
pathaccounting for the__OUTPUT__variable. -
#print_error(e, capture = true) ⇒ Object
Prints the specified
errorusing the Logger configured and handling the verbose option. -
#reset_driver ⇒ Object
Stops the test engine and starts a new engine with the same provider.
-
#start(actions = []) ⇒ Object
Starts the test engine and executes the actions specified.
-
#stop ⇒ Object
Stops the test engine.
-
#with_driver_timeout(timeout) ⇒ Object
Executes the given block using the specified driver
timeout. -
#with_timeout(*error_types) ⇒ Object
Executes the given block retrying for at most
${__TIMEOUT__}seconds. -
#with_vars(vars) ⇒ Object
Temporarily alter the value of context variables.
Constructor Details
#initialize(options) ⇒ Context
Constructs a new test context instance.
options is a hash with the following values:
- :driver
-
selenium driver symbol (defaults to
:firefox) - :timeout
-
selector timeout in seconds (defaults to
10s) - :logger
-
logger implementation name without the ‘Logger’ suffix (defaults to ‘null’ for Loggers::NullLogger).
- :verbose
-
if
true, show verbose error information (e.g. backtraces) if an error occurs (defaults tofalse) - :debug
-
if
true, break into the #debug console if an error occurs (defaults tofalse) - :wait
-
if
true, call ::wait before stopping the test engine with #stop (defaults tofalse) - :extensions
-
an array of directories that contain extensions to be loaded
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/bauxite/core/context.rb', line 138 def initialize() @options = @driver_name = ([:driver] || :firefox).to_sym @variables = { '__TIMEOUT__' => ([:timeout] || 10).to_i, '__DEBUG__' => false, '__SELECTOR__' => [:selector] || 'sid', '__OUTPUT__' => [:output], '__DIR__' => File.absolute_path(Dir.pwd) } @aliases = {} @tests = [] client = Selenium::WebDriver::Remote::Http::Default.new client.timeout = (@options[:open_timeout] || 60).to_i @options[:driver_opt] = {} unless @options[:driver_opt] @options[:driver_opt][:http_client] = client _load_extensions([:extensions] || []) @logger = Context::load_logger([:logger], [:logger_opt]) @parser = Parser.new(self) end |
Instance Attribute Details
#logger ⇒ Object (readonly)
Logger instance.
111 112 113 |
# File 'lib/bauxite/core/context.rb', line 111 def logger @logger end |
#options ⇒ Object (readonly)
Test options.
114 115 116 |
# File 'lib/bauxite/core/context.rb', line 114 def @options end |
#tests ⇒ Object
Test containers.
120 121 122 |
# File 'lib/bauxite/core/context.rb', line 120 def tests @tests end |
#variables ⇒ Object
Context variables.
117 118 119 |
# File 'lib/bauxite/core/context.rb', line 117 def variables @variables end |
Class Method Details
.action_args(action) ⇒ Object
Returns an array with the names of the arguments of the specified action.
For example:
Context::action_args 'assert'
# => [ "selector", "text" ]
616 617 618 619 |
# File 'lib/bauxite/core/context.rb', line 616 def self.action_args(action) action += '_action' unless _action_methods.include? action Action.public_instance_method(action).parameters.map { |att, name| name.to_s } end |
.actions ⇒ Object
Returns an array with the names of every action available.
For example:
Context::actions
# => [ "assert", "break", ... ]
606 607 608 |
# File 'lib/bauxite/core/context.rb', line 606 def self.actions _action_methods.map { |m| m.sub(/_action$/, '') } end |
.load_logger(loggers, options) ⇒ Object
Constructs a Logger instance using name as a hint for the logger type.
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 |
# File 'lib/bauxite/core/context.rb', line 432 def self.load_logger(loggers, ) if loggers.is_a? Array return Loggers::CompositeLogger.new(, loggers) end name = loggers log_name = (name || 'null').downcase class_name = "#{log_name.capitalize}Logger" unless Loggers.const_defined? class_name.to_sym raise NameError, "Invalid logger '#{log_name}'" end Loggers.const_get(class_name).new() end |
.loggers ⇒ Object
Returns an array with the names of every logger available.
For example:
Context::loggers
# => [ "null", "bash", ... ]
645 646 647 |
# File 'lib/bauxite/core/context.rb', line 645 def self.loggers Loggers.constants.map { |l| l.to_s.downcase.sub(/logger$/, '') } end |
.max_action_name_size ⇒ Object
Returns the maximum size in characters of an action name.
This method is useful to pretty print lists of actions
For example:
# assuming actions = [ "echo", "assert", "tryload" ]
Context::max_action_name_size
# => 7
669 670 671 |
# File 'lib/bauxite/core/context.rb', line 669 def self.max_action_name_size actions.inject(0) { |s,a| a.size > s ? a.size : s } end |
.parse_action_default(text, file = '<unknown>', line = 0) ⇒ Object
Default action parsing strategy.
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 |
# File 'lib/bauxite/core/context.rb', line 460 def self.parse_action_default(text, file = '<unknown>', line = 0) data = text.split(' ', 2) begin args_text = data[1] ? data[1].strip : '' args = [] unless args_text == '' # col_sep must be a regex because String.split has a # special case for a single space char (' ') that produced # unexpected results (i.e. if line is '"a b"' the # resulting array contains ["a b"]). # # ...but... # # CSV expects col_sep to be a string so we need to work # some dark magic here. Basically we proxy the StringIO # received by CSV to returns strings for which the split # method does not fold the whitespaces. # args = CSV.new(StringIOProxy.new(args_text), { :col_sep => ' ' }) .shift .select { |a| a != nil } || [] end { :action => data[0].strip.downcase, :args => args } rescue StandardError => e raise "#{file} (line #{line+1}): #{e.}" end end |
.parsers ⇒ Object
Returns an array with the names of every parser available.
For example:
Context::parsers
# => [ "default", "html", ... ]
655 656 657 658 659 |
# File 'lib/bauxite/core/context.rb', line 655 def self.parsers (Parser.public_instance_methods(false) \ - ParserModule.public_instance_methods(false)) .map { |p| p.to_s } end |
.selectors(include_standard_selectors = true) ⇒ Object
Returns an array with the names of every selector available.
If include_standard_selectors is true (default behavior) both standard and custom selector are returned, otherwise only custom selectors are returned.
For example:
Context::selectors
# => [ "class", "id", ... ]
631 632 633 634 635 636 637 |
# File 'lib/bauxite/core/context.rb', line 631 def self.selectors(include_standard_selectors = true) ret = Selector.public_instance_methods(false).map { |a| a.to_s.sub(/_selector$/, '') } if include_standard_selectors ret += Selenium::WebDriver::SearchContext::FINDERS.map { |k,v| k.to_s } end ret end |
.wait ⇒ Object
Prompts the user to press ENTER before resuming execution.
For example:
Context::wait
# => echoes "Press ENTER to continue" and waits for user input
425 426 427 |
# File 'lib/bauxite/core/context.rb', line 425 def self.wait Readline.readline("Press ENTER to continue\n") end |
Instance Method Details
#add_alias(name, action, args) ⇒ Object
Adds an alias named name to the specified action with the arguments specified in args.
454 455 456 |
# File 'lib/bauxite/core/context.rb', line 454 def add_alias(name, action, args) @aliases[name] = { :action => action, :args => args } end |
#debug ⇒ Object
Breaks into the debug console.
For example:
ctx.debug
# => this breaks into the debug console
269 270 271 |
# File 'lib/bauxite/core/context.rb', line 269 def debug exec_parsed_action('debug', [], false) end |
#driver ⇒ Object
Test engine driver instance (Selenium WebDriver).
259 260 261 262 |
# File 'lib/bauxite/core/context.rb', line 259 def driver _load_driver unless @driver @driver end |
#exec_action(text) ⇒ Object
Executes the specified action string handling errors, logging and debug history.
If log is true, log the action execution (default behavior).
For example:
ctx.exec_action 'open "http://www.ruby-lang.org"'
# => navigates to www.ruby-lang.org
309 310 311 312 |
# File 'lib/bauxite/core/context.rb', line 309 def exec_action(text) data = Context::parse_action_default(text, '<unknown>', 0) exec_parsed_action(data[:action], data[:args], true, text) end |
#exec_action_object(action) ⇒ Object
Executes the specified action object injecting built-in variables. Note that the result returned by this method might be a lambda. If this is the case, a further call method must be issued.
This method if part of the action execution chain and is intended for advanced use (e.g. in complex actions). To execute an Action directly, the #exec_action method is preferred.
For example:
action = ctx.get_action("echo", ['Hi!'], 'echo "Hi!"')
ret = ctx.exec_action_object(action)
ret.call if ret.respond_to? :call
540 541 542 |
# File 'lib/bauxite/core/context.rb', line 540 def exec_action_object(action) action.execute end |
#exec_file(file) ⇒ Object
Executes the specified file.
For example:
ctx.exec_file('file')
# => executes every action defined in 'file'
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/bauxite/core/context.rb', line 320 def exec_file(file) current_dir = @variables['__DIR__' ] current_file = @variables['__FILE__'] current_line = @variables['__LINE__'] @parser.parse(file) do |action, args, text, file, line| @variables['__DIR__'] = File.absolute_path(File.dirname(file)) @variables['__FILE__'] = file @variables['__LINE__'] = line break if exec_parsed_action(action, args, true, text) == :break end @variables['__DIR__' ] = current_dir @variables['__FILE__'] = current_file @variables['__LINE__'] = current_line end |
#exec_parsed_action(action, args, log = true, text = nil) ⇒ Object
Executes the specified action handling errors, logging and debug history.
If log is true, log the action execution (default behavior).
This method if part of the action execution chain and is intended for advanced use (e.g. in complex actions). To execute an Action directly, the #exec_action method is preferred.
For example:
ctx.exec_action 'open "http://www.ruby-lang.org"'
# => navigates to www.ruby-lang.org
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/bauxite/core/context.rb', line 350 def exec_parsed_action(action, args, log = true, text = nil) action = get_action(action, args, text) ret = nil if log @logger.log_cmd(action) do Readline::HISTORY << action.text ret = exec_action_object(action) end else ret = exec_action_object(action) end if ret.respond_to? :call # delayed actions (after log_cmd) ret.call else ret end rescue Selenium::WebDriver::Error::UnhandledAlertError raise Bauxite::Errors::AssertionError, "Unexpected modal present" end |
#expand(s) ⇒ Object
Recursively replaces occurencies of variable expansions in s with the corresponding variable value.
The variable expansion expression format is:
'${variable_name}'
For example:
ctx.variables = { 'a' => '1', 'b' => '2', 'c' => 'a' }
ctx. '${a}' # => '1'
ctx. '${b}' # => '2'
ctx. '${c}' # => 'a'
ctx. '${${c}}' # => '1'
690 691 692 693 694 695 696 |
# File 'lib/bauxite/core/context.rb', line 690 def (s) result = @variables.inject(s) do |s,kv| s = s.gsub(/\$\{#{kv[0]}\}/, kv[1].to_s) end result = (result) if result != s result end |
#find(selector, &block) ⇒ Object
Finds an element by selector.
The element found is yielded to the given block (if any) and returned.
Note that the recommeneded way to call this method is by passing a block. This is because the method ensures that the element context is maintained for the duration of the block but it makes no guarantees after the block completes (the same applies if no block was given).
For example:
ctx.find('css=.my_button') { |element| element.click }
ctx.find('css=.my_button').click
For example (where using a block is mandatory):
ctx.find('frame=|myframe|css=.my_button') { |element| element.click }
# => .my_button clicked
ctx.find('frame=|myframe|css=.my_button').click
# => error, cannot click .my_button (no longer in myframe scope)
252 253 254 255 256 |
# File 'lib/bauxite/core/context.rb', line 252 def find(selector, &block) # yields: element with_timeout Selenium::WebDriver::Error::NoSuchElementError do Selector.new(self, @variables['__SELECTOR__']).find(selector, &block) end end |
#get_action(action, args, text = nil) ⇒ Object
Returns an executable Action object constructed from the specified arguments resolving action aliases.
This method if part of the action execution chain and is intended for advanced use (e.g. in complex actions). To execute an Action directly, the #exec_action method is preferred.
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 |
# File 'lib/bauxite/core/context.rb', line 500 def get_action(action, args, text = nil) while (alias_action = @aliases[action]) action = alias_action[:action] args = alias_action[:args].map do |a| a.gsub(/\$\{(\d+)(\*q?)?\}/) do |match| # expand ${1} to args[0], ${2} to args[1], etc. # expand ${4*} to "#{args[4]} #{args[5]} ..." # expand ${4*q} to "\"#{args[4]}\" \"#{args[5]}\" ..." idx = $1.to_i-1 if $2 == nil args[idx] || '' else range = args[idx..-1] range = range.map { |arg| '"'+arg.gsub('"', '""')+'"' } if $2 == '*q' range.join(' ') end end end end text = ([action] + args.map { |a| '"'+a.gsub('"', '""')+'"' }).join(' ') unless text file = @variables['__FILE__'] line = @variables['__LINE__'] Action.new(self, action, args, text, file, line) end |
#get_value(element) ⇒ Object
Returns the value of the specified element.
This method takes into account the type of element and selectively returns the inner text or the value of the value attribute.
For example:
# assuming <input type='text' value='Hello' />
# <span id='label'>World!</span>
ctx.get_value(ctx.find('css=input[type=text]'))
# => returns 'Hello'
ctx.get_value(ctx.find('label'))
# => returns 'World!'
288 289 290 291 292 293 294 |
# File 'lib/bauxite/core/context.rb', line 288 def get_value(element) if ['input','select','textarea'].include? element.tag_name.downcase element.attribute('value') else element.text end end |
#output_path(path) ⇒ Object
Returns the output path for path accounting for the __OUTPUT__ variable.
For example:
# assuming --output /mnt/disk
ctx.output_path '/tmp/myfile.txt'
# => returns '/tmp/myfile.txt'
ctx.output_path 'myfile.txt'
# => returns '/mnt/disk/myfile.txt'
585 586 587 588 589 590 591 592 593 594 |
# File 'lib/bauxite/core/context.rb', line 585 def output_path(path) unless Pathname.new(path).absolute? output = @variables['__OUTPUT__'] if output Dir.mkdir output unless Dir.exists? output path = File.join(output, path) end end path end |
#print_error(e, capture = true) ⇒ Object
Prints the specified error using the Logger configured and handling the verbose option.
For example:
begin
# => some code here
rescue StandardError => e
@ctx.print_error e
# => additional error handling code here
end
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 |
# File 'lib/bauxite/core/context.rb', line 555 def print_error(e, capture = true) if @logger @logger.log "#{e.}\n", :error else puts e. end if @options[:verbose] p e puts e.backtrace end if capture and @options[:capture] with_vars(e.variables) do exec_parsed_action('capture', [] , false) e.variables['__CAPTURE__'] = @variables['__CAPTURE__'] end end end |
#reset_driver ⇒ Object
Stops the test engine and starts a new engine with the same provider.
For example:
ctx.reset_driver
=> closes the browser and opens a new one
200 201 202 203 |
# File 'lib/bauxite/core/context.rb', line 200 def reset_driver @driver.quit if @driver @driver = nil end |
#start(actions = []) ⇒ Object
Starts the test engine and executes the actions specified. If no action was specified, returns without stopping the test engine (see #stop).
For example:
lines = [
'open "http://www.ruby-lang.org"',
'write "name=q" "ljust"',
'click "name=sa"',
'break'
]
ctx.start(lines)
# => navigates to www.ruby-lang.org, types ljust in the search box
# and clicks the "Search" button.
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/bauxite/core/context.rb', line 177 def start(actions = []) return unless actions.size > 0 begin actions.each do |action| begin break if exec_action(action) == :break rescue StandardError => e print_error(e) raise unless @options[:debug] debug end end ensure stop end end |
#stop ⇒ Object
Stops the test engine.
Calling this method at the end of the test is mandatory if #start was called without actions.
Note that the recommeneded way of executing tests is by passing a list of actions to #start instead of using the #start / #stop pattern.
For example:
ctx.start(:firefox) # => opens firefox
# test stuff goes here
ctx.stop # => closes firefox
220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/bauxite/core/context.rb', line 220 def stop Context::wait if @options[:wait] begin @logger.finalize(self) rescue StandardError => e print_error(e) raise ensure @driver.quit if @driver end end |
#with_driver_timeout(timeout) ⇒ Object
Executes the given block using the specified driver timeout.
Note that the driver timeout is the time (in seconds) Selenium will wait for a specific element to appear in the page (using any of the available Selector strategies).
For example
ctx.with_driver_timeout 0.5 do
ctx.find ('find_me_quickly') do |e|
# do something with e
end
end
410 411 412 413 414 415 416 417 |
# File 'lib/bauxite/core/context.rb', line 410 def with_driver_timeout(timeout) current = @driver_timeout driver.manage.timeouts.implicit_wait = timeout yield ensure @driver_timeout = current driver.manage.timeouts.implicit_wait = current end |
#with_timeout(*error_types) ⇒ Object
Executes the given block retrying for at most ${__TIMEOUT__} seconds. Note that this method does not take into account the time it takes to execute the block itself.
For example
ctx.with_timeout StandardError do
ctx.find ('element_with_delay') do |e|
# do something with e
end
end
382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/bauxite/core/context.rb', line 382 def with_timeout(*error_types) stime = Time.new timeout ||= stime + @variables['__TIMEOUT__'].to_i yield rescue *error_types => e t = Time.new rem = timeout - t raise if rem < 0 @logger.progress(rem.round) sleep(1.0/10.0) if (t - stime).to_i < 1 retry end |
#with_vars(vars) ⇒ Object
Temporarily alter the value of context variables.
This method alters the value of the variables specified in the vars hash for the duration of the given block. When the block completes, the original value of the context variables is restored.
For example:
ctx.variables = { 'a' => '1', 'b' => '2', c => 'a' }
ctx.with_vars({ 'a' => '10', d => '20' }) do
p ctx.variables
# => {"a"=>"10", "b"=>"2", "c"=>"a", "d"=>"20"}
end
p ctx.variables
# => {"a"=>"1", "b"=>"2", "c"=>"a"}
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 |
# File 'lib/bauxite/core/context.rb', line 713 def with_vars(vars) current = @variables @variables = @variables.merge(vars) ret_vars = nil ret = yield returned = @variables['__RETURN__'] if returned == ['*'] ret_vars = @variables.clone ret_vars.delete '__RETURN__' elsif returned != nil ret_vars = @variables.select { |k,v| returned.include? k } end rescue StandardError => e e.instance_variable_set "@variables", @variables def e.variables @variables end raise ensure @variables = current @variables.merge!(ret_vars) if ret_vars ret end |