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
...