BTD
build-test-deploy Development Environment

GuiSequencer.lua

To keep the API as simple as possible, while at the same time not limiting the functionality, only basic commands are foreseen. This means that even the simplest action (for example clicking a button) is comprised of two method calls : position mouse and click button. On the other hand quite complex scenarios might exist where different dialogs or controls have to be activated before achieving the desired test environment. Furthermore everything is complicated by the asynchronous nature of main thread, idle time processing, event queue. This all calls for a sequencer :
  • the sequencer is given a list of functions to execute
  • it calls them one by one and only one at a time
  • it will wait for the current function to end before starting the next
  • once a function has ended it is not called anymore
append
Will append the desired functions to the sequencer.
local IupRobot = require('btd.lua.iuprobot')
local robot = IupRobot() -- default values for update and wait times
robot.demo = true

local GuiSequencer = require('btd.lua.guisequencer')
local seq = GuiSequencer()

local button = iup.button{title = 'this is a button'}

seq:append({robot.gotoCenter,robot,button,5},{robot.leftClick,robot,1}) -- click 'button'
This makes use of the pcall syntax and can be translated to (but will not execute these instructions) :
...
robot:gotoCenter(button,5)
robot:leftClick(1)
During execution the sequencer will wait until a function returns false or nil before going to the next function. This default behaviour can be changed by specifying an extra (first) parameter (WHICH MAY NOT BE A FUNCTION !!!) as follows :
nil
The function result will not be evaluated and the sequencer will skip to the next function.
...
seq:append({nil,some_function_with_return_value,parameters},...)
...
false
Same behaviour as when not specifying a first parameter.
...
seq:append({false,robot.gotoCenter,robot,button,5},...)
any other value (not a function nor a table)
The sequencer will wait until the specified value is returned.
For now, tables are not allowed, because that would complicate things considerably. In that case it would be best to evaluate that specific condition outside the sequencer.
...
seq:append({'function has finished',some_function_with_specified_return_value},...)
execute
Run the specified sequence.
local IupRobot = require('btd.lua.iuprobot')
local robot = IupRobot() -- default values for update and wait times
robot.demo = true

local GuiSequencer = require('btd.lua.guisequencer')
local seq = GuiSequencer()

local button = iup.button{title = 'this is a button'}

seq:append({robot.gotoCenter,robot,button,5},{robot.leftClick,robot,1}) -- click 'button'

...
function idle_cb()
  if seq:execute() then return iup.DEFAULT else return iup.CLOSE end
end
Where idle_cb will be the function which handles IUP idle time processing.
For error handling, organisation of the steps in the sequencer and to enable interaction with the test code following features were added :
multiple append statements
GUI testing often involves running the same sequence in different test methods each time taking one step more.
...

TestClass:testMethod1()
seq = GuiSequencer()
seq:append({robot.gotoCenter,robot,text,5},{robot.leftClick,robot,1})
...
function idle_cb()
  if seq:execute() then return iup.DEFAULT else return iup.CLOSE end
end
...
end

TestClass:testMethod1()
seq = GuiSequencer()
seq:append({robot.gotoCenter,robot,text,5},{robot.leftClick,robot,1})
seq:append({robot.keyClick,robot,'A'},{robot.keyClick,robot,'b'},{print,1},{robot.keyClick,robot,'B'},{robot.keyClick,robot,'A'})
...
function idle_cb()
  if seq:execute() then return iup.DEFAULT else return iup.CLOSE end
end
...
end
error reporting
Since the sequencer runs the GUI code, the error information is build up differently.
...

seq:append({robot.gotoCenter,robot,button,5},{robot.leftClicker,robot,1}) -- click 'button'

...

seq:execute()
Will display :
...
lua: step 1 statement 2
attempt to call a nill value
...
Because robot.leftClicker is a nonexisting function.
non GUI functions
The sequencer can execute any function if provisions are made that it does not wait for non GUI functions to return a nil or false value.
...

seq:append({robot.gotoCenter,robot,button,5},{robot.leftClick,robot,1}) -- click 'button'
seq:append({print,'the robot has clicked the button'},{test.equals,button_has_been_clicked,true})
...

seq:execute()
interaction with test code
It is possible to make a more elaborate sequence based on the GuiSequencer.
...
function TestIupIdle:test_click()
  local seq = GuiSequencer()
  seq:append({robot.goto,robot,0,0,1},{robot.gotoCenter,robot,button,2})
  seq:append({robot.leftDown,robot,1},{robot.wait,robot,1},{robot.leftUp,robot},{robot.wait,robot,2})
  local function idleLoop()
    return function()
      if seq:execute() then                     -- while sequence is active
        if seq.step == 1 and seq.ok then        -- step 1 and step has finished
          test.equals(2,2)
        elseif seq.step == 2 and seq.ok then    -- step 2 and step has finished
          test.equals(1,1)
        end
        return iup.DEFAULT
      else
        return iup.CLOSE
      end
    end
  end
  testIup.iupLoop(idleLoop())                   -- start idle loop processing
end
...