New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to handle modal dialog in pytest-qt without mocking the dialog #256
Comments
On way is to setup a timer which triggers a slot after a few miliseconds, giving it a chance for the dialog to show up: def test_filedialog(qtbot, window):
def handle_dialog():
# get a reference to the dialog and handle it here
QTimer.singleShot(500, handle_dialog)
qtbot.mouseClick(window.browseButton, QtCore.Qt.LeftButton, delay=1) This method unfortunately is not very reliable because you might be unlucky if the dialog takes more than 500ms to show up (due to a spike in CPU usage by another process for example). Is this dialog you trying to mock your own or Qt's? |
Thanks, @nicoddemus. This works and it solves a part of my issue 👍 Is there any way to find the reference of a dialog when it is not added to any parent control? Else would you suggest to add the dialog as a child to any other control? I guess this might help in making the dialog handling more reliable as I could wait in
I am trying to handle QFileDialog and some custom QDialog pop-ups. |
On way that I've accomplished this is to keep the reference to the dialog somewhere, for testing only. Then you can trigger your event in the next iteration of the event loop by passing class MyWindow(QWidget):
def on_browse_button(self):
self._test_dialog = QFileDialog(...)
try:
# do something with dialog
finally:
self._test_dialog = None
def test_filedialog(qtbot, window):
def handle_dialog():
while window._test_dialog is None:
qApp.processEvents()
# handle dialog now
QTimer.singleShot(0, handle_dialog)
qtbot.mouseClick(window.browseButton, QtCore.Qt.LeftButton, delay=1) I think this small hack is a small enough price for the reliability it brings to the table. |
@nicoddemus, It works fine with a small change :)
Also, in some cases, I thought it might be better to check the visibility of the dialog using
It would be great if there is an example for dialog hanlding without mocking it. Thanks a lot, @nicoddemus for this solution!! :) |
Thanks! I would love a PR updating the docs with an example like this one. Please close the issue if you don't have further questions. 👍 |
Hi @nicoddemus, I have another small query. In the above method, the reference of the dialog is stored in a data member of some class, which is used to interact with the dialog., Is there any way to find the reference of the dialog when it is not stored? |
I don't know, it should be. But I find that just storing the reference for tests is reliable and a small wart that is worth in the end. |
Sure, @nicoddemus. Thank you. |
unfortunately it is not possible to use qtbot with appropriate mouse actions to test drag-n-drop: pytest-dev/pytest-qt#256 I went with the suggestion to invoke event handlers directly. This makes the test not very useful - but still, better than nothing.
Hi, Thanks for the useful tips here @nicoddemus and @Nagendraprasath-R . I'm also writing some tests for my PyQt application and have come across this issue while trying to deal with QFileDialog. I tried your solution, but the File Dialog still hangs open when I run the test until the user interacts. I wonder, what did you mean to do in the try statement of your on_browse_button (at the My code looks like this:
So, I guess self.fd is never set to None. Any ideas how I could deal with this? Many thanks :) |
@christinab12 one option is to mock |
Handle dialog function proposed by @nicoddemus works great. I want to share my experience after writing multiple dialog tests since the application I am working on has tons of dialogs. Using a
To solve the problems mentioned above I came up with the following solution: def get_dialog(dialog_trigger: Callable, time_out: int = 5) -> QDialog:
"""
Returns the current dialog (active modal widget). If there is no
dialog, it waits until one is created for a maximum of 5 seconds (by
default).
:param dialog_trigger: Callable that triggers the dialog creation.
:param time_out: Maximum time (seconds) to wait for the dialog creation.
"""
dialog: QDialog = None
start_time = time.time()
# Helper function to catch the dialog instance and hide it
def dialog_creation():
# Wait for the dialog to be created or timeout
nonlocal dialog
while dialog is None and time.time() - start_time < time_out:
dialog = QApplication.activeModalWidget()
# Avoid errors when dialog is not created
if dialog is not None:
# Hide dialog to avoid interrupting the tests execution
# It has the same effect as close()
dialog.hide()
# Create a thread to get the dialog instance and call dialog_creation trigger
QTimer.singleShot(1, dialog_creation)
dialog_trigger()
# Wait for the dialog to be created or timeout
while dialog is None and time.time() - start_time < time_out:
continue
assert isinstance(
dialog, QDialog
), f"No dialog was created after {time_out} seconds. Dialog type: {type(dialog)}"
return dialog This function receives a dialog trigger callable (example: This function have some drawbacks:
I hope this helps someone. I might find this useful due to my application current code context and do not apply to everyone. |
I am using pytest-qt to automate the testing of a PyQt GUI. The dialogs need to be handled as a part of the testing(dialogs should not be mocked).
For example, file dialog that comes after a button-click has to be handled. There are 2 problems
After the button click command, the program control goes to the event handler and not to the next line where I can try to send mouseclick/keystrokes to the dialog.
Since the QDialog is not added to the main widget, it is not being listed among the children of the main widget. So how to get the reference of the QDialog?
I tried multi-threading but that didn't work, later I found that QObjects are not thread-safe.
The text was updated successfully, but these errors were encountered: