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

↑ top

Public Instance Methods

alias_action(name, action, *args) click to toggle source

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
assert(selector, text) click to toggle source

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
asserth(*args) click to toggle source

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
assertm(text, action = 'accept') click to toggle source

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
assertv(expected, actual) click to toggle source

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
assertw(count = 1) click to toggle source

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
break_action() click to toggle source

Prompts the user to press ENTER before resuming execution.

Note that this method provides an action named break and not break_action.

See Bauxite::Context.wait.

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
capture(file = nil) click to toggle source

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
click(selector) click to toggle source

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
debug() click to toggle source

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
doif(expected, actual, action, *args) click to toggle source

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
dounless(expected, actual, action, *args) click to toggle source

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
echo(text) click to toggle source

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
exec(*command) click to toggle source

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
exit_action() click to toggle source

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
failif(action, *args) click to toggle source

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
js(script, name = nil) click to toggle source

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(file, *vars) click to toggle source

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
open(url) click to toggle source

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
params(*vars) click to toggle source

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
replace(text, pattern, replacement, name) click to toggle source

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
reset() click to toggle source

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
return_action(*vars) click to toggle source

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
ruby(file, *vars) click to toggle source

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
select(selector, text) click to toggle source

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
set(name, value) click to toggle source

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
setif(name, value, action, *args) click to toggle source

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
source(text) click to toggle source

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
store(selector, name) click to toggle source

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
submit(selector) click to toggle source

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
test(file, name = nil, *vars) click to toggle source

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
tryload(file, *vars) click to toggle source

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(seconds) click to toggle source

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
write(selector, text) click to toggle source

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