3

In a unit test, I'm trying to send the keyboard shortcut Command+N (on Mac OS), which corresponds to a menu item in my app. I'm using the PySide.QtTest module.

In the code below, I'm using QTest.keyClicks, which doesn't produce what I'm expecting. The action corresponding to the shortcut is not called.

class AppTestCase(TestCase):

    def setUp(self):
        qApp = QApplication.instance()
        if qApp is None:
            self.app = QApplication([])
        else:
            self.app = qApp

class IdfEditorTestCase(th.AppTestCase):

    def setUp(self):
        super(IdfEditorTestCase, self).setUp()
        self.window = IdfEditorWindow()

    def test_input_object_in_new_file(self):
        if os.path.exists("current_running_test.idf"):
            os.remove("current_running_test.idf")

        self.window.selectClass("ScheduleTypeLimits")
        QTest.keyClicks(self.window, "n", Qt.ControlModifier)
        self.window.saveFileAs("current_running_test.idf")
        self.assertIdfFileContentEquals("current_running_test.idf", "ScheduleTypeLimits,,,,,;\n")

Some questions:

  • Should I send this to the window itself? Or to the menubar? Neither seem to work...
  • Is that the right way of sending a keyboard shortcut?
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
ptrico
  • 1,049
  • 7
  • 22
  • What were you expecting, and what is the actual code of the test? It's very difficult to conclude anything from what you've posted so far. – ekhumoro Dec 22 '13 at 19:59
  • Hi, I've added the rest of the test code. I'm expecting that sending the shortcut will trigger the action related to the menu item for that shortcut. Of course, I tested that the wiring of the action, menu item and shortcut work properly in the app. – ptrico Dec 23 '13 at 01:51
  • That looks like pseudo-code. What is the setup code for `self.window`, and what does `selectClass` do? You can't expect `QTest.keyClicks` to work without the GUI environment being prepared properly first. – ekhumoro Dec 23 '13 at 03:34
  • OK, I added the whole test code. In the `setUp` method of the inherited class, the QApplication is instantiated. `selectClass` selects an item in a QListView. – ptrico Dec 23 '13 at 05:55
  • So, is there a QApplication instance, and is `show()` called on the window before the test is run? – ekhumoro Dec 23 '13 at 06:11
  • See updated code for where the QApplication is created. No, `show()` is not called on the window. I added it but it still doesn't trigger the action. I had a look at [this question](http://stackoverflow.com/questions/11145583/unit-and-functional-testing-a-pyside-based-application) prior to all this and `show()` is not called there either. Should it? – ptrico Dec 23 '13 at 08:06
  • Yes and no: please see my answer. – ekhumoro Dec 23 '13 at 21:10

1 Answers1

4

For "normal" key-click tests (like entering text in a line-edit), it is not necessary to show the window. This is in line with what you'd expect if you sent key events to a hidden widget during normal running of the application.

But for testing shortcuts, the target window must be shown - which is again in line with what you'd expect. A keyboard shortcut should not activate commands during normal running if the target window is not visible.

So your setup code should probably include something like this:

    self.window.show()
    QTest.qWaitForWindowShown(self.window)

The qWaitForWindowShown call is necessary on systems where windows are shown asynchronously (for Qt5, use qWaitForWindowExposed).

EDIT:

Here's a test script that works for me:

import unittest
from PySide.QtCore import Qt
from PySide.QtGui import QApplication, QMainWindow, QLineEdit
from PySide.QtTest import QTest

class Window(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        menu = self.menuBar().addMenu('File')
        menu.addAction('Test', self.handleTest, 'Ctrl+N')
        self.edit = QLineEdit(self)
        self.setCentralWidget(self.edit)

    def handleTest(self):
        self.edit.setText('test')

class AppTestCase(unittest.TestCase):
    def setUp(self):
        qApp = QApplication.instance()
        if qApp is None:
            self.app = QApplication([])
        else:
            self.app = qApp

class WindowTestCase(AppTestCase):
    def setUp(self):
        super(WindowTestCase, self).setUp()
        self.window = Window()
        self.window.show()
        QTest.qWaitForWindowShown(self.window)

    def test_input_object_in_new_file(self):
        text = 'test'
        self.assertNotEqual(text, self.window.edit.text())
        QTest.keyClicks(self.window, 'n', Qt.ControlModifier)
        self.assertEqual(text, self.window.edit.text())

    def test_enter_text(self):
        text = 'foobar'
        self.assertNotEqual(text, self.window.edit.text())
        QTest.keyClicks(self.window.edit, text)
        self.assertEqual(text, self.window.edit.text())

if __name__ == "__main__":

    unittest.main()

UPDATE:

Here's a PyQt5 version of the above script:

import unittest
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QLineEdit
from PyQt5.QtTest import QTest

class Window(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        menu = self.menuBar().addMenu('File')
        menu.addAction('Test', self.handleTest, 'Ctrl+N')
        self.edit = QLineEdit(self)
        self.setCentralWidget(self.edit)

    def handleTest(self):
        self.edit.setText('test')

class AppTestCase(unittest.TestCase):
    def setUp(self):
        qApp = QApplication.instance()
        if qApp is None:
            self.app = QApplication([''])
        else:
            self.app = qApp

class WindowTestCase(AppTestCase):
    def setUp(self):
        super(WindowTestCase, self).setUp()
        self.window = Window()
        self.window.show()
        QTest.qWaitForWindowExposed(self.window)

    def test_input_object_in_new_file(self):
        text = 'test'
        self.assertNotEqual(text, self.window.edit.text())
        QTest.keyClicks(self.window, 'n', Qt.ControlModifier)
        self.assertEqual(text, self.window.edit.text())

    def test_enter_text(self):
        text = 'foobar'
        self.assertNotEqual(text, self.window.edit.text())
        QTest.keyClicks(self.window.edit, text)
        self.assertEqual(text, self.window.edit.text())

if __name__ == "__main__":

    unittest.main()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thanks for the explanation. However, even with showing the window and waiting for it to be displayed, the action is still not triggered when the shortcut is invoked. Something else must be wrong but I can't put my finger on it. – ptrico Dec 24 '13 at 03:15
  • @ptrico. Added a test script that works for me (only tried on Linux, though). – ekhumoro Dec 24 '13 at 03:28
  • I appreciate your effort in helping me out. The test script doesn't work on my setup (Mac OS 10.8, Python 3.3, Qt 4.8, PySide 1.2.1). I tried with PyQt 4.10 with same result. – ptrico Dec 24 '13 at 04:06
  • @ptrico. Bah. Looks like it might be an OSX-specific issue then. I'm afraid I can't investigate any further, because I only have access to Linux and WinXP boxes. One thing you might want to try is running my example test-case with an event-loop going (which surely _must_ work, but it still might be worth confirming). PS: Just tested my script on WinXP, and it does work there as well. – ekhumoro Dec 24 '13 at 04:17
  • Yep. I'll accept your answer for the part about showing the window and waiting for it to displayed. Cheers. – ptrico Dec 24 '13 at 04:23
  • Looks like I have the same problem as @ptrico: QTest.keyClick doesn't seem to work on OS X with QAction shortcuts, even with waitForWindowShown, timers, etc.. Pressing keys manually works though. No problem with other operating systems. -- OS X 10.10.5, Python 3.4 (Anaconda 64-bit), Qt 4.8.6, PyQt 4.11.3. – Cyrille Rossant Oct 01 '15 at 19:39