Home · All Namespaces · All Classes · Grouped Classes · Modules · Functions codeless banner

Creating a QtUiTest Test Widget

Introduction

QtUiTest provides support for simulating high-level user interface interactions. Conceptually these take the form of, for example, Select 'Dog' from the widget labelled 'Animal', or Enter 'Bob' into the widget labelled 'Name'.

Crucially, testcases do not need to specify the exact type of each widget or how to interact with it. Instead the logic for interacting with different types of widgets resides on the system under test, either alongside the implementation of each widget or in a plugin. Each class of widget which QtUiTest can interact with has a corresponding wrapper class, referred to as a test widget.

This document explains when a test widget must be created and how it is implemented.

When a Test Widget is Required

QtUiTest includes support for most widgets used throughout Qt Extended. When a new type of widget is introduced, a test widget may be required.

In these cases, a test widget will almost certainly have to be implemented:

In these cases, a test widget will usually not be required:

As an example, consider a testcase for creating a contact in the addressbook application. At one point in the testcase, we wish to set the contact's title to Doctor:

select("Dr", "Title");

This requires that the widget referred to by "Title" implements the SelectWidget interface. If this is not the case, the testcase fails with a failure message like the following:

FAIL! : sys_addressbook::creating_a_contact() ERROR: Title (QComboBox(0x80b2768)) is not a SelectWidget.

This error message indicates that the "Title" widget, which is a QComboBox, does not have any corresponding test widget which implements the SelectWidget interface, and therefore can't have items selected from it.

Note that not all errors arising from missing test widgets will be of this form.

Choosing the Right Interfaces

Test widgets must implement one or more of a standard set of interfaces belonging to the QtUiTest namespace.

Test widget interfaces map to the conceptual purpose of a widget from a user's perspective. The available interfaces are listed below:

InterfaceApplies toExamples
WidgetAll 2D user interface elements.QWidget
ActivateWidgetWidgets which are activated to trigger some action.QAbstractButton
CheckWidgetWidgets which can be checked and unchecked.QCheckBox, QRadioButton
TextWidgetWidgets which display text.QLineEdit, QTextEdit, QMenu, QAbstractItemView, QLabel, many others
InputWidgetWidgets which can accept text input.QLineEdit, QTextEdit
ListWidgetWidgets which display a list of items.QAbstractItemView, QMenu
SelectWidgetWidgets which allow an item to be selected from a list.QAbstractItemView, QMenu

Each test widget should implement all interfaces applicable to the wrapped widget. Test widgets can subclass other test widgets and reuse already-implemented interfaces. For example, the test widget for QCheckBox could inherit from the test widget for QAbstractButton to avoid having to reimplementing the Widget, TextWidget and ActivateWidget interfaces again.

Some test widget interfaces are strongly related and are likely to be implemented in pairs. Almost all widgets which accept text input also display the entered text, so any test widget which implements InputWidget will usually implement TextWidget. Almost all widgets which can be selected from also display the selectable items, so any test widget which implements SelectWidget will usually implement ListWidget.

Implementing the Test Widget

To make a new test widget visible to QtUiTest, there are two separate approaches which can be taken, each with their own advantages.

Plugin method

The plugin method involves adding the test widget code into a plugin which is then loaded by QtUiTest at runtime. This is the most suitable method to use in most cases and the only method used for the test widgets shipped with QtUiTest.

Advantages to the plugin method compared to the multiple inheritance method include:

Test widget class

Each test widget class needs to inherit from QObject and the relevant test widget interfaces.

In practice, it is common for a test widget class hierarchy to be written which closely mirrors the wrapped widget class hierarchy. This makes it possible to avoid rewriting the code for common interfaces such as QtUiTest::Widget many times.

It is possible to subclass the test widgets shipped with QtUiTest, although they are not guaranteed to remain source or binary compatible across releases. The convention used in the reference plugins to generate a test widget class name is to take the wrapped widget class name, drop any leading Q, and prefix Test. For example, the test widget wrappers for QWidget and QAbstractItemView are named TestWidget and TestAbstractItemView respectively.

Using the plugin approach, while subclassing from TestWidget to avoid reimplementing the QtUiTest::Widget interface, would result in a class declaration like the following:

    #include <testwidget.h>
    class TestCustomComboBox : public TestWidget, public QtUiTest::ListWidget,
        public QtUiTest::SelectWidget, public QtUiTest::TextWidget
    {
    Q_OBJECT
    Q_INTERFACES(
        QtUiTest::ListWidget
        QtUiTest::SelectWidget
        QtUiTest::TextWidget)

    public:
        TestCustomComboBox(CustomComboBox* wrapped);
        virtual ~TestCustomComboBox();

        // QtUiTest::ListWidget members
        virtual QStringList list() const;
        virtual QRect visualRect(const QString&) const;

        // QtUiTest::SelectWidget members
        virtual bool canSelect(QString const&) const;
        virtual bool select(QString const&);

        // QtUiTest::TextWidget members
        virtual QString text() const;
        virtual QString selectedText() const;

    private:
        CustomComboBox* m_wrapped;
    };

Implementing the test widget is as simple as retrieving the necessary information from the wrapped widget. Test widgets can create and use other test widgets at runtime when necessary, as shown in the list() function below.

    TestCustomComboBox::TestCustomComboBox(CustomComboBox* wrapped)
        : m_wrapped(wrapped)
    {}

    QStringList TestCustomComboBox::list() const
    { return qtuitest_cast<QtUiTest::ListWidget*>(m_wrapped->view())->list(); }

    QString TestCustomComboBox::text() const
    { return list().join("\n"); }

    QString TestCustomComboBox::selectedText() const
    { return m_wrapped->currentText(); }

Memory management is handled automatically; there will be a maximum of one TestCustomComboBox instance created for any CustomComboBox and it will be destroyed when the underlying CustomComboBox is destroyed.

Test widget factory

When using the plugin approach it is also necessary to implement a factory class. This serves as the entry point to the plugin and handles the logic for creating test widgets.

The test widget factory must subclass QtUiTest::WidgetFactory and implement the create() function to create test widgets and the keys() function to report which widget classes can be wrapped.

    class TestWidgetsFactory : public QObject, public QtUiTest::WidgetFactory
    {
        Q_OBJECT
        Q_INTERFACES(QtUiTest::WidgetFactory)

    public:
        TestWidgetsFactory();

        virtual QObject* create(QObject*);
        virtual QStringList keys() const;
    };

The create() function is called when a new test widget is to be created. Our example widget factory handles CustomComboBox widgets and nothing else.

    QObject* TestWidgetsFactory::create(QObject* wrapped)
    {
        if ((CustomComboBox* ccb = qobject_cast<CustomComboBox*>(wrapped))) {
            return new TestCustomComboBox(ccb);
        }
        return 0;
    }

The keys() function must report which classes can be handled by this factory. Any object passed into the create() function is guaranteed to be one of the classes returned by keys(). Classes are handled from most to least specific; when creating a test widget for CustomComboBox, a factory which handles CustomComboBox has higher priority over a factory which handles QWidget. If two or more factories handle the same class, it is undefined which factory will be asked to create the test widget.

Our example factory can only handle CustomComboBox widgets so it returns that class name only.

    QStringList TestWidgetsFactory::keys() const
    {
        return QStringList() << "CustomComboBox";
    }

Finally, the plugin needs to be exported using the standard Qt plugin mechanism:

    #include <qplugin.h>
    Q_EXPORT_PLUGIN2(customtestwidgets, TestWidgetsFactory)

In the project's qbuild.pro, the PLUGIN_TYPE must be set to qtuitest_widgets.

Multiple inheritance method

The multiple inheritance approach requires the widget being wrapped to implement the test widget interfaces itself.

Advantages to the multiple inheritance method compared to the plugin method include:

For example, implementing a custom combobox class along with all of the associated test widget interfaces would result in a class declaration like:

    class CustomComboBox : public QComboBox, public QtUiTest::Widget,
        public QtUiTest::ListWidget, public QtUiTest::SelectWidget,
        public QtUiTest::TextWidget
    {
    Q_OBJECT
    Q_INTERFACES(
        QtUiTest::Widget
        QtUiTest::ListWidget
        QtUiTest::SelectWidget
        QtUiTest::TextWidget)

    public:
        CustomComboBox(QWidget* parent = 0);
        ...

        // CustomComboBox members
        void addCustomItem(const QVariant& item);
        void addCustomItems(const QVariantList& items);
        ...

        // QtUiTest::Widget members
        virtual const QRect& geometry() const;
        virtual QRect rect() const;
        ...

        // QtUiTest::ListWidget members
        virtual QStringList list() const;
        virtual QRect visualRect(const QString&) const;
        ...

        // etc...
    };

Implementing the test widget is as simple as returning the necessary information. Test widgets can create and use other test widgets at runtime when necessary, as shown in the list() function below.

    QStringList CustomComboBox::list() const
    { return qtuitest_cast<QtUiTest::ListWidget*>(view())->list(); }

    QString CustomComboBox::text() const
    { return list().join("\n"); }

    QString CustomComboBox::selectedText() const
    { return currentText(); }

Customizing Behavior for Standard UI Elements

In test cases it is possible to refer to abstract UI elements such as the home screen or the soft menu bar with code such as the following:

    select("New contact...", optionsMenu());

In the above example, the optionsMenu() function automatically resolves to whatever widget is responsible for showing the options menu for the standard Qt Extended user interface. Similar functions include tabBar(), progressBar(), softMenu() and launcherMenu().

If the Qt Extended UI is heavily customized, it may be necessary to override the behavior of these functions. WidgetType enumerates the types of widgets which can have their resolution overridden this way.

Overriding the behavior for a particular WidgetType requires a WidgetFactory to be implemented (as detailed above) and the find() function to be overridden. This function may return an actual widget or a test widget. Returning a test widget is useful in cases where a particular WidgetType does not map to exactly one actual widget.

For example, consider a device with two significant customizations. The options menu, typically a simple QMenu raised by a context key on the device, has been moved to an always-onscreen singleton widget. The device can also accept and hangup calls by sliding the top half of the device up or down (which is seen by Qt as a special key click), as well as through the typical dedicated keys and items in the options menu.

In this example, for the options menu it is sufficient for find() to simply return a pointer to the new options menu widget. For the call manager, find() must construct and return a special test widget which knows how to simulate all the necessary key clicks for call management as well as interacting with the options menu.

The following find() function achieves this:

    QObject* CustomFactory::find(QtUiTest::WidgetType type)
    {
        // For the options menu, use our permanently onscreen widget
        if (type == QtUiTest::OptionsMenu)
            return OnScreenOptionsMenu::instance();

        // For the call manager, create our test widget which knows how to simulate
        // device-specific key clicks.  Since there is no underlying widget,
        // just create and reuse a single test widget throughout the whole lifetime
        // of the program.
        if (type == QtUiTest::CallManager) {
            static TestCustomCallManager testCallManager;
            return &testCallManager;
        }

        // For all other widget types, just use the default find behavior.
        return 0;
    }


Copyright © 2009 Trolltech Trademarks
Qt Extended 4.4.3