GCF LOGO VCL LOGO
GCF Investigator

GCF Investigator is a tool-set that allows you to record, edit and play automated test scripts for GCF applications. Investigator is used only for creating UI test cases. For all other kinds, you can use QtTestLib or any other testing tool of your choice. The tool-set consists of the following

  • Investigator Agent Component - this component is loaded into an application to either record or run a test case.
  • Recorder - this program loads the agent component into an application and triggers record of a test case.
  • Runner - this program loads the agent component into an application and triggers running of a test case.

This page explains what Investigator can do and what it cannot do with the help of tutorials and examples.

Tutorial

Consider a simple GCFGui3 application that when launched shows a window as follows.

tutorial-window.png

In this application we have connected signals and slots between the sliders and dial in such a way that their values are always configured to be in sync. For instance if the value of dial is changed, then the values of the slider also change. The form itself is designed in Qt Designer. The application simply loads the form and shows it.

Applications that are being tested using GCF Investigator are called AUT (or Application Under Test). In the next section you will see how to structure code of your AUT.

Implementing the AUT

Shown below is the code of an AUT that would load the form as described before.

#include <GCF3/GuiApplication>
#include <GCF3/GuiComponent>
#include "ui_Form.h"
int main(int argc, char **argv)
{
GCF::GuiApplication a(argc, argv);
QWidget *formWindow = new QWidget;
Ui::Form ui;
ui.setupUi(formWindow);
formWindow->show();
GCF::Component *formComponent = new GCF::Component;
formComponent->addContentObject("formWindow", formWindow);
return a.processArgumentsAndExec();
}

The following paragraphs explain each aspect of the main() function implementation.

You start by creating an instance of GCF::GuiApplication as follows.

int main(int argc, char **argv)
{
GCF::GuiApplication a(argc, argv);

Then you create a formWindow and show it. It is important to create formWindow on the heap; because later on you will be adding the form window to a component as a content object. GCF deletes all content objects when the component is unloaded (which happens when the application is shuting down). We dont want GCF to attempt deleting an object on the stack!

QWidget *formWindow = new QWidget;
Ui::Form ui;
ui.setupUi(formWindow);
formWindow->show();

Next you create a GCF::Component instance and add the formWindow as a content object to it. This part is important because GCF Investigator expects to find root windows in the object-tree of the application.

GCF::Component *formComponent = new GCF::Component;
formComponent->load();
formComponent->addContentObject("formWindow", formWindow);

Finally, you should make use of the processArgumentsAndExec() function to run the event loop of your application. This part is done to ensure that the –loadComponents command-line argument is parsed and processed. It is through this command-line argument that the Recorder and Runner tools reqest loading of the Agent component into your AUT. NOTE: if processArgumentsAndExec() is not used - then you will not be able to record or run test case scripts in your application.

return a.processArgumentsAndExec();
}

Creating a UI test script for the AUT

To create a AUT test script, launch the Recorder tool from the command prompt as follows.

$ cd /path/to/aut-binary
$ $GCFDIR/Investigator/Recorder ./AUT TestScript1.js
Note
Here $GCFDIR refers to an environment variable that points to the Binary folder of the directory where you have compiled GCF3.
Here AUT is the name of the application executable file.

When you launch Recorder, as described above, the following happens

  • Recorder will inject Agent component into the AUT and trigger its loading.
  • Recorder will send a request for recording of all user actions on the AUT into a script called TestScript1.js

Suppose you click on the dial in the AUT and notice its value changing. You then close the application and open TestScript1.js in a text editor. You will notice the following code

function test_function() {
wait(750)
var dial = object("Application.GCF_Component.formWindow/dial")
dial.mouseClickAtPos(144, 31, Qt.LeftButton, Qt.NoModifier)
}
Note
The code generated on your computer may be slightly different.

Later, you can read more about test script syntax and the environment in which the scripts are run. But at this point it is enough to know

  • test_xxx() functions in the test script are called one after the other, when the test script is run.
  • the object() method allows you to reference named objects in the application
  • you can trigger events by using methods like mouseClickAtPos()
  • you can introduce spin waits using the wait() method

Adding validation checks

Now suppose that you want to include validation checks in the script. Lets say you wanted to verify ane ensure that values of dial, horizontal and vertical sliders are in sync. You would modify the test script code as follows

function test_function() {
wait(750)
var dial = object("Application.GCF_Component.formWindow/dial")
dial.mouseClickAtPos(144, 31, Qt.LeftButton, Qt.NoModifier)
log("Dial value is now: " + dial.value)
var verticalScrollBar = object("Application.GCF_Component.formWindow/verticalScrollBar")
var horizontalScrollBar = object("Application.GCF_Component.formWindow/horizontalScrollBar")
verify(dial.value === horizontalScrollBar.value)
verify(dial.value === verticalScrollBar.value)
}

The verify() method passed the test function if the condition passed in it was true. It fails the test function and reports it otherwise. You can learn more about these functions in a later section.

Running the test script

You can now run the test script using the Runner tool as follows

$ cd /path/to/aut-binary
$ $GCFDIR/Investigator/Runner ./Tutorial TestScript1.js
********* Start testing of Tutorial *********
Using GCF 3.0.0 on Qt 5.1.1
Scheduling test-script 'TestScript1.js' for execution after 0 seconds
Running script 'TestScript1.js'
test_function {
    Dial value is now: 45
}
SUCCESS:
********* Finished testing of Tutorial *********

When you launched the Runner tool, as described above, the following happend

  • Runner injected Agent component into the AUT and triggered its loading.
  • Runner then requested for TestScript1.js to be loaded and executed.
  • Agent component, which was loaded into the AUT, processes the TestScript1.js file and exectes all test_xxx() functions.
  • It then dumps the test results to console

Example - Calculator Application

Qt comes with a standard inbuilt 'calculator' example. Lets alter the code of that example a little bit and see how we can use GCF Investigator to test it. Start Qt Creator and open the 'Calculator Example'.

qt-calculator.png

After opening the example, compile and execute it. Just to make sure that it works.

qt-calculator-run.png

You can now start GCFying the application by altering the project file (calculator.pro) as follows

QT += script widgets uitools
RESOURCES += calculator.qrc
SOURCES += main.cpp
CONFIG -= app_bundle
CONFIG += GCFGui3 # to include GCF's Gui module
qtHaveModule(scripttools): QT += scripttools

Next, alter the main.cpp file as follows

#include <QApplication>
....
....
#include <GCF3/GuiApplication>
#include <GCF3/Component>
int main(int argc, char **argv)
{
Q_INIT_RESOURCE(calculator);
// Instead of QApplication use, GCF::GuiApplication
GCF::GuiApplication app(argc, argv);
.....
.....
.....
ui->show();
// Create a UI component and add ui as content object into it
uiComp->load();
uiComp->addContentObject("ui", ui);
// call processArgumentsAndExec() instead of exec
return app.processArgumentsAndExec();
}

Now compile and execute the application. Just to make sure that it works after all the modifications.

gcf-calculator-run.png

What you have accomplished with all these changes is that you have made the calculator example testable with Investigator.

Record test script

You can launch the Recorder tool to start recording all user actions into a test case.

$ cd /path/to/calculator-binary
$ $GCFDIR/Investigator/Recorder ./calculator TestScript1.js

Suppose that you wanted to test whether the SQRT button on the calculator works properly or not. You could the calculator program and "feed" user actions to compute the square-root of say 198; and have Recorder capture all the user actions into a script. When you open TestScript1.js; you will find the following auto-generated code

function test_function() {
wait(3000)
var changeSignButton = object("Application.GCF_Component.ui/changeSignButton")
var calculator = object("Application.GCF_Component.ui")
var pointButton = object("Application.GCF_Component.ui/pointButton")
var twoButton = object("Application.GCF_Component.ui/twoButton")
var oneButton = object("Application.GCF_Component.ui/oneButton")
oneButton.mouseClickAtPos(23, 14, Qt.LeftButton, Qt.NoModifier)
oneButton.mousePressAtPos(23, 14, Qt.LeftButton, Qt.NoModifier)
oneButton.mouseMove(24, 10)
var fiveButton = object("Application.GCF_Component.ui/fiveButton")
var nineButton = object("Application.GCF_Component.ui/nineButton")
nineButton.mouseClickAtPos(13, 22, Qt.LeftButton, Qt.NoModifier)
nineButton.mousePressAtPos(13, 22, Qt.LeftButton, Qt.NoModifier)
nineButton.mouseMove(12, 22)
var eightButton = object("Application.GCF_Component.ui/eightButton")
eightButton.mouseClickAtPos(30, 22, Qt.LeftButton, Qt.NoModifier)
eightButton.mousePressAtPos(30, 22, Qt.LeftButton, Qt.NoModifier)
eightButton.mouseMove(32, 22)
var divisionButton = object("Application.GCF_Component.ui/divisionButton")
var squareRootButton = object("Application.GCF_Component.ui/squareRootButton")
squareRootButton.mouseClickAtPos(29, 22, Qt.LeftButton, Qt.NoModifier)
squareRootButton.mousePressAtPos(29, 22, Qt.LeftButton, Qt.NoModifier)
squareRootButton.mouseMove(29, 21)
var clearAllButton = object("Application.GCF_Component.ui/clearAllButton")
var display = object("Application.GCF_Component.ui/display")
display.mouseClickAtPos(219, 20, Qt.LeftButton, Qt.NoModifier)
wait(750)
display.mousePressAtPos(219, 20, Qt.LeftButton, Qt.NoModifier)
display.mouseMove(218, 20)
}

Insert validation code

At the moment the Recorder doesnt capture user actions in a optimized way. So some edits are required. Shown below is a modified (and shorter) version of the same code.

function test_sqrt() {
wait(750)
var oneButton = object("Application.GCF_Component.ui/oneButton")
oneButton.mouseClick(Qt.LeftButton, Qt.NoModifier)
var nineButton = object("Application.GCF_Component.ui/nineButton")
nineButton.mouseClick(Qt.LeftButton, Qt.NoModifier)
var eightButton = object("Application.GCF_Component.ui/eightButton")
eightButton.mouseClick(Qt.LeftButton, Qt.NoModifier)
var display = object("Application.GCF_Component.ui/display")
verify(display.text === "198") // validation code
var squareRootButton = object("Application.GCF_Component.ui/squareRootButton")
squareRootButton.mouseClick(Qt.LeftButton, Qt.NoModifier)
var sqrtValue = 14.0712472795
verify(display.text == sqrtValue, "Incorrect square root of 198") // validation code
}

The above code is now easier to follow. It simulates mouse click on 1, 9 and 8 buttons. Ensures that the display is showing "198". Then it sumulates mouse click on the square root button and verifies that the result shown in the square-root is appropriate.

Run the test script and validate the application

You can now make use of Runner to run the test script and check if the application passes the test.

$ cd /path/to/calculator-binary
$ $GCFDIR/Investigator/Runner ./Calculator TestScript1.js
********* Start testing of Calculator *********
Using GCF 3.0.0 on Qt 5.1.1
Scheduling test-script 'TestScript1.js' for execution after 0 seconds
Running script 'TestScript1.js'
test_function {
    FAILED at line 21: Incorrect square root of 198
}
FAILURE: 1 of 1 test case(s) failed
********* Finished testing of Calculator *********

The test case failed because the calculator uses Math.sqrt() javascript function to evaluate square root of 198. And that function returns 14.071247279470288; a value more precise than 14.0712472795. You can either perform a verification with error tolerance or compare the result directly against Math.sqrt(198). The following code snippet shows how to do the latter

function test_sqrt() {
....
....
var sqrtValue = Math.sqrt(198)
verify(display.text == sqrtValue, "Incorrect square root of 198") // validation code
}

Now use Runner to run the test script and check if the application passes the test.

$ cd /path/to/calculator-binary
$ $GCFDIR/Investigator/Runner ./Calculator TestScript1.js
********* Start testing of Calculator *********
Using GCF 3.0.0 on Qt 5.1.1
Scheduling test-script 'TestScript1.js' for execution after 0 seconds
Running script 'TestScript1.js'
test_function {
}
SUCCESS:
********* Finished testing of Calculator *********

Sure enough it does!

GCF Investigator - Agent

Agent is a GCF component that can be loaded into any GCF application to either record user actions into a script or run a test script and print results. It is a light-weight component that makes a AUT testable.

The Agent component can be loaded explicitly by the AUT as follows

gAppService->loadComponent("Investigator/Agent");

But that is a non-standard way to load the Agent component. A better way is to write your AUT such that it makes use of the GCF::Application::processArgumentsAndExec() function to trigger the event loop. You can then inject the Agent component into the application by making use of the –loadComponents commandline argument.

$ cd /path/to/aut
$ ./aut --loadComponents:Investigator/Agent

Command line parameters

When you load the Agent component into a AUT, it looks for certain command line arguments passed to the AUT. If none of the command line arguments are found, then the Agent will trigger AUT to quit immediately.

Parameter Description
--record

Requests Agent to record user actions into a test script. The script will be dumped on standard output when the AUT is closed, unless the --outputFile parameter is used. Example:

$ ./AUT --loadComponents:Investigator/Agent --record
--outputFile:<output-file-name>

Requests Agent to dump the recorded test script into a file. Example:

$ ./AUT --loadComponents:Investigator/Agent --record --outputFile:/path/to/output-file.js
--testScript:<script-file-name>

Requests Agent to process run the specified test script on the AUT. Example:

$ ./AUT --loadComponents:Investigator/Agent --testScript:/path/to/test-script.js
--testStartDelay:<n>

Requests Agent to start processing the test script after n seconds. By default the test script is run immediately. Example:

$ ./AUT --loadComponents:Investigator/Agent --testScript:/path/to/test-script.js --testStartDelay:10

Script environment

When Agent is used to run a test script, it creates an environment inside of which the script files are loaded and executed. This environment offers all enumerations defined in the Qt namespace (example: Qt.LeftButton, Qt.Key_A etc..). It also offers the following functions

log(message)

This function can be used to log anything into the output of the test. The log will show up within the context created for the test function that is currently being run. Example:

function test_function() {
log("this is my log message")
}

When the above test script is executed, you would get the following result

$ cd /path/to/calculator-binary
$ ./Calculator --loadComponents:Investigator/Agent --testScript:script.js
********* Start testing of calculator *********
Using GCF 3.0.0 on Qt 5.1.1
Scheduling test-script 'script.js' for execution after 0 seconds
Running script 'script.js'
test_function {
    this is my log message
}
SUCCESS:
********* Finished testing of calculator *********

fail(message)

This function can be used to log a message and immediately mark the current test function as failed. The fail() function never returns to the caller. It immediately quits the test_function() and causes Agent to move on to the next test function. Example:

function test_function() {
log("log message before fail")
fail("fail message")
log("log message after fail")
}

When the above test script is executed, you would get the following result

$ cd /path/to/calculator-binary
$ ./Calculator --loadComponents:Investigator/Agent --testScript:script.js
********* Start testing of calculator *********
Using GCF 3.0.0 on Qt 5.1.1
Scheduling test-script 'script.js' for execution after 0 seconds
Running script 'script.js'
test_function {
    log message before fail
    FAILED at line 3: fail message
}
FAILURE: 1 of 1 test case(s) failed
********* Finished testing of calculator *********

object(path)

This function is used to search for QObject instances in your application and return a reference to them for use in your test scripts. The function accepts as parameter the path of the object being searched. The path is a slash '/' seperated set of names that dig into the trees of objects in the AUT.

  • The first component of path is complete name of an object in the AUT's GCF::ApplicationServices::objectTree(). Example: "Application.Component.Object".
  • From then on each component of the path could be
    • Name of a child object. Example: "Application.Component.Object/childName/grandChildName"
    • Indexed class-name of the child object. Example: "Application.Component.Object/QClassName[1]/AnotherClass[2]"
    • Mixture of the above. Example: "Application.Component.Object/childName/QClassName[1]/grandChildName"
  • Special names for special child objects
    • If the path of an object leads to a QAbstractScrollArea (or its subclasses), then
      • viewport will fetch its viewport widget. Example "Application.C.O/QTreeView[0]/viewport"
      • horizontalscrollbar will fetch the horizontal scroll-bar of the scroll area
      • verticalscrollbar will fetch the vertical scroll-bar of the scroll area
    • If the path leads to a QAbstractItemView (or its subclasses), then
      • viewport, horizontalsrollbar, verticalscrollbar will work as it would for a QAbstractScrollArea
      • model will fetch the view's model
      • delegate will fetch the view's delegate
      • selectionmodel will fetch the selection model of the view
      • horizontalheader will fetch the view's horizontal header
      • verticalheader will fetch the view's vertical header
    • If the path leads to a QGraphicsView (or its subclasses), then
      • viewport, horizontalsrollbar, verticalscrollbar will work as it would for a QAbstractScrollArea
      • scene would fetch the view's scene
      • names of QGraphicsObject items in the scene can be used to fetch items in the scene
    • If the path leads to a QQuickView (or its subclasses), then
      • engine will return pointer to the QQmlEngine used by the QQuickView
      • names of context properties (set using QQmlEngine::rootContext()->setContextProperty(...)) can be used to fetch object corresponding to the context property.

The object(string) function returns a object variable. Depending on the type of object returned certain functions and properties will be made available. For example if the returned object is a QWidget then you can access mousePress(), mouseClick(), mouseRelease() etc. The following table lists out all such object-type specific functions and properties.

Object type Functions
QObject
  • parent: Returns parent of this object
  • object(string): Returns a child object referenced by the string path parameter. Path parameter convention is same as above.
QWidget, QWindow
  • Inherits properties and functions from QObject
  • keyboardFocus: true if the widget (or window) has keyboard focus. False otherwise
  • setGeometry(x,y,width,height): Sets geometry of the widget.
  • resize(width,height): Resizes the widget.
  • mousePress(button,modifiers): Simulates mouse press on the widget. button is value from Qt.MouseButton enumeration. And modifiers is a value from is Qt.KeyboardModifiers enumeration.
  • mousePressAtPos(x,y,button,modifiers): Simulates mouse press on the widget at a particular position.
  • mouseRelease(button,modifiers): Simulates mouse release on the widget. button is value from Qt.MouseButton enumeration. And modifiers is a value from is Qt.KeyboardModifiers enumeration.
  • mouseReleaseAtPos(x,y,button,modifiers): Simulates mouse release on the widget at a particular position.
  • mouseMove(x,y): Simulates mouse move on the widget at a particular position.
  • mouseClick(button,modifiers): Simulates mouse click on the widget
  • mouseClickAtPos(x,y,button,modifiers): Simulates mouse click on the widget at a specific position
  • mouseDClick(button,modifiers): Simulates mouse double click on the widget
  • mouseDClickAtPos(x,y,button,modifiers): Simulates mouse double click on the widget at a specific position
  • keyPress(modifiers,key): Simulates key press on the widget. The key parameter can be a value from Qt.Key enumeration.
  • keyRelease(modifiers,key): Simulates key release on the widget
  • keyClick(modifiers,key): Simulates key click on the widget
  • keyClicks(modifiers,text): Simulates key click on the widget
  • waitForShown(): Waits for widget to be shown
  • wheel(factor,modifiers): Simulates a wheel event on the widget
  • wheelAtPos(x,y,factor,modifiers): Simulates a wheel event on the widget at a specific position
  • show(): Shows the widget
  • hide(): Hides the widget
QGraphicsObject
  • Inherits properties and functions from QObject
  • center: returns an object whose x and y properties return contain the center of the item
  • rect: returns an object whose x, y, width and height properties contain the rectangle of the item with respect to the scene
  • mousePress(button,modifiers)
  • mousePressAtPos(x,y,button,modifiers)
  • mouseRelease(button,modifiers)
  • mouseReleaseAtPos(x,y,button,modifiers)
  • mouseMove(x,y)
  • mouseClick(button,modifiers)
  • mouseClickAtPos(x,y,button,modifiers)
  • wheel(factor,modifiers)
  • wheelAtPos(x,y,factor,modifiers)

interface(name)

This function takes as input name of a class or interface and returns an object that implements the interface. Example:

function test_function() {
var obj = interface("GCF::IGDriveLite")
if(obj) {
....
}
}

wait(milliseconds)

This function spin waits for as many milliseconds as specified in the parameter. During the spin-wait the event loop will be running.

waitForSignal(object,signal,timeout)

This function waits for emission of a signal in an object for a maximum timeout milliseconds duration. The function returns an object whose properties are parameters emitted by the signal. Example:

function test_function() {
var webView = interface("QWebView")
var sigObj = waitForSignal(webView, "titleChanged(QString)", 5000)
if(sigObj === false)
log("signal was not emitted within 5 seconds")
else
log("title was " + sigObj.title)
}

waitForPropertyChange(object,property,timeout)

This function waits for a change in property to occur in object for a max of timeout milliseconds The function returns value of the property, if a change was detected, false otherwise. Example:

function test_function() {
var webView = interface("QWebView")
var title = waitForPropertyChange(webView, "title", 5000)
if(title === false)
log("title did not change within 5 seconds")
else
log("title changed to " + title)
}

compare(v1,v2,msg)

This function compares v1 with v2. If the values were equal then the function returns otherwise it prints msg and terminates the calling test function immediately.

verify(condition,msg)

This function does nothing if condition is true. It prints msg and terminates the calling test function immediately, otherwise.

createSignalSpy(object,signal)

This function creates a signal-spy on signal of object and returns the spy. On the returned spy object you can invoke

  • wait(timeout) function to wait for timeout milliseconds for a signal emission to occur. The function would return true if a emission was detected within the timout, false otherwise.
  • count property to find out then number of emissions captured by the spy
  • at(index) function to fetch a array of parameters from the index 'th emission
  • takeAt(index) function same as index, but this function removes the index 'th emission from the spy
  • clear() function to remove all emissions from the spy

Example:

function test_function() {
var spy = createSignalSpy( interface("QWebView"), "titleChanged(QString)" )
...
...
if( spy.wait() ) {
var title = spy.takeAt( spy.count-1 )[0]
// ...
}
}

GCF Investigator - Test Script

The script executed by Agent component on the AUT is called "Test Script". Test scripts are JavaScript files that have a bunch of test_xxx() functions. These test functions are called one after the other, in the same order that they are declared. In addition to the test_xxx() functions, a test script can also contain the following optional functions

  • initTestCase() - this function is called before any other test function in the script
  • init() - this function is called before each test function in the script
  • cleanup() - this function is called after each test function in the script
  • cleanupTestCase() - this function is called after all test functions have been called in the script

NOTE: it is perfectly fine to have other functions in the test script. As long as they dont start with test_ they are not automatically called by the Agent component.

GCF Investigator - Recorder

The Recorder application simplifies the process of launching an AUT and recording user actions into a test script. Usage:

$GCFDIR/Investigator/Recorder <AUT> [output-test-script] [--autArgs <arg1> [arg2] [arg3] ...]

Here

  • AUT is a required command-line argument. It must contain the complete path of AUT.
  • output-test-script is an optional command-line argument that could contain the path of the test script file into which the captured user actions are dumped.
  • Arguments passed after –autArgs are passed as command line arguments to the AUT as is

The advantage of using Recorder is that it can report AUT crashes on the terminal, to which you can respond and do something about.

GCF Investigator - Runner

The Runner application simplifies the process of launching an AUT and running a test-script on it. Usage:

$GCFDIR/Investigator/Runner <AUT> <test-script> [startup-delay] [--autArgs <arg1> [arg2] [arg3] ...]

Here

  • AUT is a required command-line argument. It must contain the complete path of AUT.
  • test-script is required command-line argument that should contain the path of the test script file, which needs to be executed on the AUT.
  • startup-delay is an optional command-line argument that would specify the number of seconds to wait before running the script on AUT.
  • Arguments passed after –autArgs are passed as command line arguments to the AUT as is

The advantage of using Runner is that it can report AUT crashes on the terminal, to which you can respond and do something about.