2

My task is to write an automated UI test for a software being developed. It happens so, that there are radiobuttons triggering a messagebox, which stops my automated test until I manually confirm it (pressing ENTER). The issue is that I do not know how I can call that newly summoned messagebox and then have it confirmed by QTest::keyClick(<object>, QtKey::Key_Enter); and have my automated test continue the run.

I am using QWidgets, QApplication, Q_OBJECT and QtTest. I will provide a code similar to what I am working with:

void testui1(){
Form1* myform = new Form1();
myform->show();
QTest::mouseClick(myform->radioButton01, Qt::LeftButton, Qt::NoModifier, QPoint(), 100);
// Message box pops up and stops the operation until confirmed
QTest::mouseClick(myform->radioButton02, Qt::LeftButton, Qt::NoModifier, QPoint(), 100);
// again
...
}

How exactly can I script to confirm the message box automatically? The message box is only an [OK] type, so I don't need it to return whether I have pressed Yes or No. A QTest::keyClick(<target object>, Qt::Key_Enter) method needs to know to which object it should press enter to. I tried including myform into the object and it did not work. Googling I did not find the answer. I found the following result as not functioning for what I am looking for

QWidgetList allToplevelWidgets = QApplication::topLevelWidgets();
foreach (QWidget *w, allToplevelWidgets) {
    if (w->inherits("QMessageBox")) {
        QMessageBox *mb = qobject_cast<QMessageBox *>(w);
        QTest::keyClick(mb, Qt::Key_Enter);
    }
}
Unit1
  • 95
  • 8
  • Does your code finds the message box? Please clarify. – vahancho Jun 26 '17 at 13:14
  • I doubt it does. In any case is there a piece of script to find that message box? – Unit1 Jun 26 '17 at 14:53
  • Your code looks valid: iterate over all top level windows. I may suspect that the modal message box blocks the event loop, so that your code does not execute while message box is opened. – vahancho Jun 26 '17 at 15:02
  • I believe you need to register a timer which will close the message box - please see edits to my answer below – Steve Lorimer Jun 26 '17 at 19:42

1 Answers1

5

The problem is that once you've "clicked" on your radio button, which results in QMessageBox::exec being called, your code stops running until the user clicks on of the buttons.

You can simulate the user clicking a button by starting a timer before you click on the radio button.

In the callback function for the timer you can use QApplication::activeModalWidget() to obtain a pointer to the message box, and then just call close on it.

QTimer::singleShot(0, [=]()
    {
        QWidget* widget = QApplication::activeModalWidget();
        if (widget)
            widget->close();
    });

If you want to press a specific key on the message box, you can use QCoreApplication::postEvent to post a key event to the message box

QTimer::singleShot(0, [=]()
    {
        int key = Qt::Key_Enter; // or whatever key you want

        QWidget* widget = QApplication::activeModalWidget();
        if (widget)
        {
            QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); 
            QCoreApplication::postEvent(widget, event);
        }
    });

So in terms of how this ties into the sample code you gave:

void testui1()
{
    Form1* myform = new Form1();
    myform->show();

    QTimer::singleShot(0, [=]()
        {
            QWidget* widget = QApplication::activeModalWidget();
            if (widget)
                widget->close();
            else
                QFAIL("no modal widget");
        });

    QTest::mouseClick(myform->radioButton01, Qt::LeftButton, Qt::NoModifier, QPoint(), 100);
}

A sample app putting all the above together:

#include <QApplication>
#include <QMainWindow>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTimer>
#include <QMessageBox>
#include <iostream>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    QMainWindow window;

    QWidget widget;
    window.setCentralWidget(&widget);

    QHBoxLayout layout(&widget);

    QPushButton btn("go");
    layout.addWidget(&btn);

    QObject::connect(&btn, &QPushButton::clicked, [&]()
        {
            QTimer::singleShot(0, [=]()
                {
                    QWidget* widget = QApplication::activeModalWidget();
                    if (widget)
                    {
                        widget->close();
                    }
                    else
                    {
                        std::cout << "no active modal\n";
                    }
                });

            QMessageBox(
                QMessageBox::Icon::Warning,
                "Are you sure?",
                "Are you sure?",
                QMessageBox::Yes | QMessageBox::No,
                &window).exec();
        });

    window.show();
    return app.exec();
}

Clicking on Go will look like nothing is happening, as the QMessageBox is closed immediately.

To prove that the message box is shown and then closed you can increase the time in the call to QTimer::singleShot to a value such as 1000. Now it will show the message box for 1 second, and then it will be closed by the timer.

Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
  • Thanks a lot friend! But in the piece `QTimer::singleShot(0, [=]()` the 0 should be replaced with a value of time to wait. In my case I put 150 as milliseconds value in it. It works! – Unit1 Jun 27 '17 at 09:46
  • @Unit1 adding an arbitrary delay is very brittle and error prone - 150ms works for you on your dev machine, but will it work on *every* machine? I would stick with a delay of 0, but add in a call to `QCoreApplication::processEvents();` before you check for the active modal (untested, just an idea, if it works it will be far superior to using a delay) – Steve Lorimer Jun 27 '17 at 15:06
  • Delay=0 did not work for me. The debugging script has stopped. I tested it prior to setting the Delay to 150 ms. | Where do you suggest should I place the `QCoreApplication::processEvents();` ? Before the mouseClick summoning the messagebox or inside that QTimer? – Unit1 Jun 28 '17 at 10:03
  • @Unit1 inside the timer before the call to get active modal – Steve Lorimer Jun 28 '17 at 13:08
  • `QCoreApplication::processEvents();` That didn't work. I did put it just before the activemodalwidget. The message box appeared again and stopped the automation. – Unit1 Jul 03 '17 at 09:45
  • @Unit1 I'm not sure why you require the magic number for the timer time, nor why processEvents isn't working - I've updated my answer to give a sample app which shows it is working for me. Perhaps there is something which QTest is doing which is different to an actual app which I'm not aware of (I don't use QTest, so can't comment on that I'm afraid) – Steve Lorimer Jul 03 '17 at 21:23