class Bauxite::Action
Test action class.
Test actions are basic test operations that can be combined to create a test case.
Test actions are implemented as public methods of the Action class.
Each test action is defined in a separate file in the 'actions/'
directory. The name of the file must match the name of the action. Ideally,
these files should avoid adding public methods other than the action method
itself. Also, no attr_accessors
should be added.
Action methods can use the ctx
attribute to refer to the current test Context.
For example (new action template):
# === actions/print_source.rb ======= # class Action # :category: Action Methods def print_source # action code goes here, for example: puts @ctx.driver.page_source. end end # === end actions/print_source.rb === # Context::actions.include? 'print_source' # => true
To avoid name clashing with Ruby reserved words, the '_action' suffix can be included in the action method name (this suffix will not be considered part of the action name).
For example (_action suffix):
# === actions/break.rb ======= # class Action # :category: Action Methods def break_action # do something end end # === end actions/break.rb === # Context::actions.include? 'break' # => true
If the action requires additional attributes or private methods, the name of the action should be used as a prefix to avoid name clashing with other actions.
For example (private attributes and methods):
# === actions/debug.rb ======= # class Action # :category: Action Methods def debug _debug_do_stuff end private @@debug_line = 0 def _debug_do_stuff @@debug_line += 1 end end # === end actions/debug.rb === # Context::actions.include? 'debug' # => true
Action methods support delayed execution of the test action. Delayed execution is useful in cases where the action output would break the standard logging interface.
Delayed execution is implemented by returning a lambda from the action method.
For example (delayed execution):
# === actions/break.rb ======= # class Action # :category: Action Methods def break_action lambda { Context::wait } end end # === end actions/break.rb === # Context::actions.include? 'debug' # => true
Executing this action would yield something like the following:
break [ OK ] Press ENTER to continue
While calling Bauxite::Context.wait directly would yield:
break Press EN TER to continue [ OK ]
Action Methods
↑ topPublic Instance Methods
Aliases name
to action
with additional arguments.
In args
the variables ${1}
..${n}
will be expanded to the arguments given to the alias. Also
${n*}
will be expanded to the space separated list of
arguments from the n-th on. Finally, ${n*q}
will behave like
${n*}
except that each argument will be surrounded by quotes
(+“+) and quotes inside the argument will be doubled (+”“+).
Note that action
can be any action except alias
.
Also note that this action does not check for cyclic aliases (e.g. alias
a
to b
and alias b
to
a
). You should check that yourself.
Also note that this method provides an action named alias
and
not alias_action.
For example:
alias hey echo "$1, nice to see you!" hey john # => this would expand to # echo "john, nice to see you!"
# File lib/bauxite/actions/alias.rb, line 48 def alias_action(name, action, *args) @ctx.add_alias(name, action, args) end
Asserts that the value of the selected element matches text
.
text
is a regular expression. text
can be
surrounded by /
characters followed by regular expression
flags.
For example:
# assuming <input type="text" id="hello" value="world" /> assert hello world assert hello wor assert hello ^wor assert hello /WorlD/i # => these assertions would pass
# File lib/bauxite/actions/assert.rb, line 38 def assert(selector, text) @ctx.with_timeout Bauxite::Errors::AssertionError do @ctx.find(selector) do |e| actual = @ctx.get_value(e) unless actual =~ _pattern(text) raise Bauxite::Errors::AssertionError, "Assertion failed: expected '#{text}', got '#{actual}'" end true end end end
Replays the current GET request and asserts that the HTTP headers returned
by that request match each of the args
specified.
Note that this action results in an additional HTTP GET request to the current browser url.
The syntax of args
is:
"header_name1=expression1" "header_name2=expression2" ...
Where expression
is a regular expression. Note that multiple
headers can be asserted in a single asserth call. Also note that if the
same header is specified more than once, the value of the header must match
every expression specified.
For example:
# assuming response headers { 'Content-Type' => 'text/plain' } asserth "content-type=plain" asserth "content-type=^text" "content-type=/plain$" # => these assertions would pass
# File lib/bauxite/actions/asserth.rb, line 46 def asserth(*args) uri = URI(@ctx.driver.current_url) res = Net::HTTP.get_response(uri) args.each do |a| name,value = a.split('=', 2); name = name.strip.downcase value = value.strip actual = res[name] || '' unless actual =~ _pattern(value) raise Bauxite::Errors::AssertionError, "Assertion failed for HTTP Header '#{name}': expected '#{value}', got '#{actual}'" end end end
Asserts that a native modal popup is present (e.g. alert, confirm, prompt,
etc.) and that its text matches the specified text
.
text
can be a regular expression. See assert for more details.
The second action
parameter specifies the action to be
performed on the native popup. The default action is to accept
the popup. Alternatively, the action specified could be to
dismiss
the popup.
For example:
# assuming the page opened an alert popup with the "hello world!" # text assertm "hello world!" # => this assertion would pass
# File lib/bauxite/actions/assertm.rb, line 40 def assertm(text, action = 'accept') ctx.with_timeout Selenium::WebDriver::Error::NoAlertPresentError do a = @ctx.driver.switch_to.alert unless a.text =~ _pattern(text) raise Bauxite::Errors::AssertionError, "Assertion failed: expected '#{text}', got '#{a.text}'" end a.send(action.to_sym) end end
Asserts that the actual
text matches the expected
text.
expected
can be a regular expression. See assert for more details.
For example:
# assuming ctx.variables['myvar'] = 'myvalue1234' assertv "^myvalue\d+$" "${myvar}" # => this assertion would pass
# File lib/bauxite/actions/assertv.rb, line 34 def assertv(expected, actual) unless actual =~ _pattern(expected) raise Bauxite::Errors::AssertionError, "Assertion failed: '#{actual}' does not match '#{expected}'" end true end
Asserts that the number of currently open windows equals
count
.
For example:
assertw # => this assertion would pass (only the main window is open) js "window.w = window.open()" assertw 2 # => this assertion would pass (main window and popup) js "setTimeout(function() { window.w.close(); }, 3000);" assertw 1 # => this assertion would pass (popup was closed)
# File lib/bauxite/actions/assertw.rb, line 39 def assertw(count = 1) @ctx.with_timeout Bauxite::Errors::AssertionError do unless @ctx.driver.window_handles.size == count.to_i raise Bauxite::Errors::AssertionError, "Assertion failed: all popups must be closed." end true end end
Prompts the user to press ENTER before resuming execution.
Note that this method provides an action named break
and not
break_action.
For example:
break # => echoes "Press ENTER to continue" and waits for user input
# File lib/bauxite/actions/break.rb, line 36 def break_action lambda { Bauxite::Context::wait } end
Captures a screenshot of the current browser window and saves it with
specified file
name. If file
is omitted a file
name will be generated based on the value of __TEST__
,
__FILE__
and CAPTURE_SEQ
. If set, the
value of __OUTPUT__
will be prefixed to file
(unless file
is an absolute path). The last captured file name
will be stored in __CAPTURE__
.
For example:
capture # => this would capture the screenshot with a generated file name. capture my_file.png # => this would capture the screenshot to my_file.png in the current # output directory.
# File lib/bauxite/actions/capture.rb, line 40 def capture(file = nil) unless file seq = @ctx.variables['__CAPTURE_SEQ__'] || 0 test = @ctx.variables['__TEST__'] @ctx.variables['__CAPTURE_SEQ__'] = seq + 1 file = @ctx.variables['__FILE__'] || '' file = file[Dir.pwd.size+1..-1] if file.start_with? Dir.pwd file += "_#{seq}" file = "#{test}_#{file}" if test file = file.gsub(/[^A-Z0-9_-]/i, '_') + '.png' end file = @ctx.output_path(file) @ctx.driver.save_screenshot(file) @ctx.variables['__CAPTURE__'] = file true end
Triggers the click
event on the selected element.
For example:
# assuming <button type="button" id="btn">click me</button> click btn # => this would click the button
# File lib/bauxite/actions/click.rb, line 32 def click(selector) @ctx.find(selector) { |e| e.click } true end
Breaks into the debug console.
In the debug console you can type action strings and test their result.
The debug console supports a history of previously executed actions and
autocomplete (pressing the TAB
key).
For example:
debug # => this breaks into the debug console
# File lib/bauxite/actions/debug.rb, line 35 def debug lambda do @ctx.with_vars({ '__DEBUG__' => true }) do _debug_process end end end
Executes action
only if expected
matches
actual
.
The conditional check in this action is similar to assertv.
For example:
set first john set last doe doif john ${first} assertv doe ${last} # => this assertion would pass. doif smith ${last} load smith_specific_text.bxt # => this would only load smith_specific_text.bxt if the last # variable matches 'smith'
# File lib/bauxite/actions/doif.rb, line 39 def doif(expected, actual, action, *args) return false unless actual =~ _pattern(expected) @ctx.exec_action_object(@ctx.get_action(action, args)) end
Executes action
only if expected
does not match
actual
.
The conditional check in this action is similar to assertv.
For example:
set first john set last doe dounless james ${first} assertv doe ${last} # => this assertion would pass. dounless false ${load_captcha} load captcha.bxt # => this would only load captcha.bxt if the load_captcha # variable is not 'false'
# File lib/bauxite/actions/dounless.rb, line 39 def dounless(expected, actual, action, *args) return false if actual =~ _pattern(expected) @ctx.exec_action_object(@ctx.get_action(action, args)) end
Prints the value of the specified text
.
text
is subject to variable expansion (see Bauxite::Context#expand).
For example:
echo "Hello World!" # => this would print "Hello World!" in the terminal window.
# File lib/bauxite/actions/echo.rb, line 33 def echo(text) true end
Executes command
, optionally storing the results in a
variable.
If the first argument of command
is name=...
the
results of the execution will be assigned to the variable named
name
.
For example:
exec "that_day=date --date='2001-01-01' | cut -f 1 -d ' '" echo "${that_day}" # => this would print 'Mon'
# File lib/bauxite/actions/exec.rb, line 35 def exec(*command) data = command[0].split('=', 2) name = nil if (data.size == 2) name = data[0] command[0] = data[1] end ret = %x`#{command.join(' ')}` @ctx.variables[name] = ret.strip if name end
Aborts the execution of the current context.
For example:
exit assertv true false # => the assertv will NOT be executed
# File lib/bauxite/actions/exit.rb, line 32 def exit_action :break end
Executes the specified action
expected to fail. If
action
succeeds the failif action fails. If
action
fails, failif
succeeds.
failif effectively negates the
value of action
. Note that this method is intended to negate
assertions (e.g. assert, assertv, source, etc.). The behavior when
applied to other actions (e.g. load, ruby, test, etc.) is undefined.
For example:
# assuming <input type="text" id="hello" value="world" /> assert hello world failif assert hello universe failif assertv true false # => these assertions would pass
# File lib/bauxite/actions/failif.rb, line 40 def failif(action, *args) @ctx.with_timeout Bauxite::Errors::AssertionError do begin @ctx.with_vars({ '__TIMEOUT__' => 0}) do @ctx.exec_parsed_action(action, args, false) end rescue Bauxite::Errors::AssertionError, Selenium::WebDriver::Error::NoSuchElementError return true end raise Bauxite::Errors::AssertionError, "Assertion did not failed as expected:#{action} #{args.join(' ')}" end end
Executes the specified Javascript script
, optionally storing
the results the variable named name
.
Note that if name
is provided, the script must return a value
using the Javascript return
statement.
For example:
js "return document.title" title_var echo "${title_var}" # => this would print the title of the page
# File lib/bauxite/actions/js.rb, line 36 def js(script, name = nil) result = @ctx.driver.execute_script(script) @ctx.variables[name] = result if name true end
Load the specified file into an isolated variable context and execute the actions specified. If the file does not exist, this action fails. See tryload for a similar action that skips if the file does not exist.
file
can be a path relative to the current test file.
An optional list of variables can be provided in vars
. These
variables will override the value of the context variables for the
execution of the file (See Bauxite::Context#with_vars).
The syntax of the variable specification is:
"var1_name=var1_value" "var2_name=var2_value" ...
For example:
load other_test.bxt "othervar=value_just_for_other" echo "${othervar}" # => this would load and execute other_test.bxt, injecting othervar # into its context. After other_test.bxt completes, othervar will # be restored to its original value (or be undefined if it didn't # exist prior to the 'load' call).
# File lib/bauxite/actions/load.rb, line 46 def load(file, *vars) tryload(file, *vars) || (raise Bauxite::Errors::FileNotFoundError, "File not found: #{file}") end
Opens the specified url
in the browser.
For example:
open "http://www.ruby-lang.org" # => this would open http://www.ruby-lang.org in the browser window
# File lib/bauxite/actions/open.rb, line 31 def open(url) @ctx.driver.navigate.to url true end
Asserts that the variables named vars
are defined and not
empty.
For example:
params host db_name username password # => this would fail if any of the four variables listed above # is not defined or is empty
# File lib/bauxite/actions/params.rb, line 32 def params(*vars) missing = vars.select { |v| (@ctx.variables[v] || '') == '' }.join(', ') if missing != '' raise Bauxite::Errors::AssertionError, "Assertion failed: the following variables must be defined and not be empty: #{missing}." end true end
Replaces the occurrences of pattern
in text
with
replacement
and assigns the result to the variable named
name
.
For example:
set place "World" replace "Hello ${place}" "World" "Universe" greeting echo "${greeting}!" # => this would print 'Hello Universe!'
# File lib/bauxite/actions/replace.rb, line 34 def replace(text, pattern, replacement, name) @ctx.variables[name] = text.gsub(_pattern(pattern), replacement) end
Resets the test engine by closing and reopening the browser. As a side effect of resetting the test engine, all cookies, logins and cache items are destroyed.
For example:
reset # => this would close and re-open the browser window, removing # cookies, cache, login sessions, etc.
# File lib/bauxite/actions/reset.rb, line 34 def reset() @ctx.reset_driver true end
Returns the specified variables to the parent scope (if any).
If vars
is *
every variable defined in the
current scope will be returned to the parent scope.
The syntax of the variable specification is:
"var1_name" "var2_name" ...
Note that this method provides an action named return
and not
return_action.
For example:
set result "42" return result # => this would inject the result variable (whose value is 42) # into the calling context.
Full example (see load, write, click, store and assert for more details):
# in main.bxt load login.txt "username=jdoe" "password=hello world!" # in login.bxt write id=user "${username}" write id=pass "${password}" click id=login store id=loginName fullName return fullName # back in main.bxt assert id=greeting "Welcome ${fullName}!" # => this assertion uses the variable returned from login.bxt
# File lib/bauxite/actions/return.rb, line 58 def return_action(*vars) if vars == ['*'] @ctx.variables['__RETURN__'] = vars return true end rets = @ctx.variables['__RETURN__'] || [] @ctx.variables['__RETURN__'] = rets + vars unless rets.include? '*' true end
Load the specified ruby file into an isolated variable context and execute the ruby code.
file
can be a path relative to the current test file.
An optional list of variables can be provided in vars
. See load.
The ruby action file must contain a single lambda that takes a Context instance as its only argument.
For example:
# === my_test.rb ======= # lambda do |ctx| ctx.exec_action 'echo "${message}"' ctx.driver.navigate.to 'http://www.ruby-lang.org' ctx.variables['new'] = 'from ruby!' ctx.exec_action 'return new' end # === end my_test.rb === # # in main.bxt ruby my_test.rb "message=Hello World!" echo "${new}" # => this would print 'from ruby!'
# File lib/bauxite/actions/ruby.rb, line 50 def ruby(file, *vars) # _load_file_action is defined in tryload.rb _load_file_action(file, *vars) do |f| content = '' File.open(f, 'r') { |ff| content = ff.read } eval(content).call(@ctx) end || (raise Bauxite::Errors::FileNotFoundError, "File not found: #{file}") end
Sets the value of the selected HTMLSelect
to
text
.
text
can be the value
or the text
of
the target HTMLOption
.
For example:
# assuming <select id="s"> # <option value="one">First</option> # <option value="two">Second</option> # </select> select s Second select s two # => both actions select the second option.
# File lib/bauxite/actions/select.rb, line 38 def select(selector, text) @ctx.find(selector) do |e| e = Selenium::WebDriver::Support::Select.new(e) begin e.select_by(:value, text) rescue Selenium::WebDriver::Error::NoSuchElementError e.select_by(:text, text) end end end
Sets the variable named name
to the value
specified.
Both name
and value
are subject to variable
expansion (see Bauxite::Context#expand).
For example:
set one "uno" set two "${one} + ${one}" echo "${two}" # => this would print 'uno + uno'
# File lib/bauxite/actions/set.rb, line 36 def set(name, value) @ctx.variables[name] = value end
Sets the variable named name
to the value
specified only if the. action
execution succeeds. If the
execution fails, the value of name
is left unchanged.
For example:
set name john setif is_john true assertv "/John/i" "${name}" assertv true ${is_john} # => the assertion would pass
# File lib/bauxite/actions/setif.rb, line 35 def setif(name, value, action, *args) begin @ctx.exec_parsed_action(action, args, false) @ctx.variables[name] = value true rescue Bauxite::Errors::AssertionError return false end end
Asserts that the page source matches text
.
text
can be a regular expression. See assert for more details.
For example:
# assuming <html><body>Hello World!</body></html> source "Hello.*!" # => this assertion would pass
# File lib/bauxite/actions/source.rb, line 34 def source(text) @ctx.with_timeout Bauxite::Errors::AssertionError do actual = @ctx.driver.page_source verbose = @ctx.options[:verbose] ? "\nPage source:\n#{actual}" : '' unless actual =~ _pattern(text) raise Bauxite::Errors::AssertionError, "Assertion failed: page source does not match '#{text}'#{verbose}" end true end end
Sets the variable named name
to the value of the selected
element.
name
is subject to variable expansion (see Bauxite::Context#expand).
For example:
# assuming <span id="name">John</span> store id=name firstName echo "${firstName} # => this would print 'John'
# File lib/bauxite/actions/store.rb, line 35 def store(selector, name) @ctx.find(selector) { |e| @ctx.variables[name] = @ctx.get_value(e) } end
Submits the form that contains the selected element.
For example:
# assuming <form><input id="i"/></form> submit i # => this would submit the form
# File lib/bauxite/actions/submit.rb, line 32 def submit(selector) @ctx.find(selector) do |e| e.submit end end
Load file
using the load action into a new test context.
If name
is specified, it will be used as the test name.
An optional list of variables can be provided in vars
. These
variables will override the value of the context variables for the
execution of the file (See Bauxite::Context#with_vars).
If any action in the test context fails, the whole test context fails, and the execution continues with the next test context (if any).
For example:
test mytest.bxt "My Test" my_var=1 # => this would load mytest.bxt into a new test context # named "My Test", and within that context ${my_var} # would expand to 1.
# File lib/bauxite/actions/test.rb, line 42 def test(file, name = nil, *vars) delayed = load(file, *vars) name = name || file lambda do begin t = Time.new status = 'ERROR' error = nil @ctx.with_vars({ '__TEST__' => name }) do delayed.call status = 'OK' end rescue StandardError => e @ctx.print_error(e) error = e ensure @ctx.tests << { :name => name, :status => status, :time => Time.new - t, :error => error } end end end
Load the specified file into an isolated variable context and execute the actions specified. If the file does not exist, this action skips. See load for a similar action that fails if the file does not exist.
file
can be a path relative to the current test file.
An optional list of variables can be provided in vars
. These
variables will override the value of the context variables for the
execution of the file (See Bauxite::Context#with_vars).
The syntax of the variable specification is:
"var1_name=var1_value" "var2_name=var2_value" ...
For example:
tryload other_test.bxt tryload nonexistent_file.bxt # => this would load and execute other_test.bxt, then silently fail # to execute nonexistent_file.bxt without failing the test.
# File lib/bauxite/actions/tryload.rb, line 46 def tryload(file, *vars) _load_file_action(file, *vars) do |f| @ctx.exec_file(f) end end
Wait for the specified number of seconds
.
For example:
wait 5 # => this would wait for 5 seconds and then continue
# File lib/bauxite/actions/wait.rb, line 31 def wait(seconds) seconds = seconds.to_i seconds.times do |i| @ctx.logger.progress(seconds-i) sleep(1.0) end end
Sets the value of the selected element to text
.
text
is subject to variable expansion (see Bauxite::Context#expand).
For example:
# assuming <input type="text" name="username" /> write name=username "John" # => this would type the word 'John' in the username textbox
# File lib/bauxite/actions/write.rb, line 34 def write(selector, text) @ctx.find(selector) do |e| begin e.clear rescue Selenium::WebDriver::Error::UnknownError # user-editable... # do nothing (if this should fail, it will in the line below) end e.send_keys(text) end end