I received some amazing PyQt5 code from @Taras Melnyk in this question, which runs great as a standalone script. What I'm trying to do is turn that standalone script into a function that I can run as many times as I need to. I've done that, and the function below runs fine when I run it one time. However, a subsequent call to the function hangs and then exits the Python console. I've done a lot of reading on this trying to find a fix. I've learned how PyQt5 (Python wrapper for the C++ library) implements its own errors inside its threads, so it is very hard to catch those errors in Python. I've tried several solutions from several posts, with no luck catching the error (you can see these tries in the code below, with relevant links commented above them). What am I missing here, so that I can run this function multiple times without it hanging and erroring out? NOTE: excuse the print() statements - I just wanted a quick debug. I know logging is better and I do use it frequently. import json import traceback import requests from PyQt5.QtWidgets import QApplication from PyQt5.QtWebEngineWidgets import QWebEngineView from PyQt5.QtCore import QUrl, Qt import sys from PyQt5.uic.properties import QtCore # https://stackoverflow.com/questions/40227047/python-pyqt5-how-to-show-an-error-message-with-pyqt5/47275100#47275100 def my_exception_hook(exctype, value, traceback): # Print the error and traceback print(exctype, value, traceback) # Call the normal Exception hook after sys.__excepthook__(exctype, value, traceback) # Back up the reference to the exceptionhook sys._excepthook = sys.excepthook # Set the exception hook to our wrapping function sys.excepthook = my_exception_hook class SAMLBrowser(QWebEngineView): def __init__(self, idp_url, pvwa_cookie, on_saml_response): super().__init__() self.idp_url = idp_url self.pvwa_cookie = pvwa_cookie self.on_saml_response = on_saml_response self.loadFinished.connect(self.check_for_saml_response) self.navigate_to_idp() self.saml_token = "" # Make sure the window shows up on top so we can easily see it # https://stackoverflow.com/questions/50631443/how-to-make-application-window-stay-on-top-in-pyqt5 self.setWindowFlags(Qt.Window | Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint) def navigate_to_idp(self): print(f"inside SAMLBrowser.navigate_to_idp - running setUrl with idp_url '{self.idp_url}'") try: self.setUrl(QUrl(self.idp_url)) except Exception as e: print(f"inside SAMLBrowser.navigate_to_idp - Oh no something went wrong!") traceback.print_exc() print(f"inside SAMLBrowser.navigate_to_idp - setUrl complete") def check_for_saml_response(self): """After every load, check for presence of SAMLResponse.""" # Evaluate JavaScript to inspect the contents of the webpage print(f"inside SAMLBrowser.check_for_saml_response - calling process_page") self.page().toHtml(self.process_page) print(f"inside SAMLBrowser.check_for_saml_response - call to process_page complete") def process_page(self, html): print(f"inside SAMLBrowser.process_page") if "SAMLResponse" in html: print(f"inside SAMLBrowser.process_page - FOUND SAMLRESPONSE!!!") # This assumes the SAMLResponse is part of an <input> field self.page().runJavaScript('document.getElementsByName("SAMLResponse")[0].value', self.handle_saml_response) print(f"inside SAMLBrowser.process_page - runJavaScript() complete") def handle_saml_response(self, saml_response): print(f"inside SAMLBrowser.handle_saml_response") if saml_response: print(f"inside SAMLBrowser.handle_saml_response - FOUND SAMLRESPONSE!!!") # Trim and pass response back to the parent self.saml_token = self.on_saml_response(saml_response) print(f"inside SAMLBrowser.handle_saml_response - set saml_token successfully.") print(f"inside SAMLBrowser.handle_saml_response - calling .close()") self.close() print(f"inside SAMLBrowser.handle_saml_response - .close() called successfully") def saml_authentication(pvwa_address): session = requests.Session() # 1. First POST request to get the IDP URL and cookies logon_url = f"{pvwa_address}/PasswordVault/api/auth/saml/Logon" print(f"Getting initial IDP URL from Password Vault") try: response = session.post(logon_url, json={}, verify=False) response.raise_for_status() idp_url = response.json() print(f"Initial IDP URL returned as: {idp_url}") except Exception as e: print(f"Failed to fetch IDP URL: {e}") return None # Extract 'CA88888' cookie ca88888_cookie = None print(f"Looking in session cookies for the 'CA88888' cookie") for cookie in session.cookies: if cookie.name == "CA88888": print(f"Found the 'CA88888' cookie") ca88888_cookie = cookie.value if not ca88888_cookie: print("Could not extract CA88888 cookie.") return None # 2. Launch the SAML browser to interact with the IDP login page def on_saml_response(saml_response): print(f"Inside saml_authentication.on_saml_response") # Submit the SAML response to complete authentication body = { "concurrentSession": "true", "apiUse": "true", "SAMLResponse": saml_response.strip() } headers = {'Content-Type': 'application/x-www-form-urlencoded'} print(f"Adding 'CA88888' cookie to our session cookies, in preparation for our final call to PWV to get our token") # Use a new session to send back with cookies session.cookies.set("CA88888", ca88888_cookie, domain=pvwa_address.split("/")[2], path="/") try: final_response = session.post(logon_url, data=body, headers=headers, verify=False) final_response.raise_for_status() print(f"final call to PWV made") # print("Session Token:", final_response.text) return final_response.text except Exception as e: print(f"Failed to retrieve session token: {e}") try: print(f"inside saml_authentication - starting QApplication") app = QApplication(sys.argv) print(f"inside saml_authentication - starting SAMLBrowser") browser = SAMLBrowser(idp_url, ca88888_cookie, on_saml_response) print(f"inside saml_authentication - running browser.show()") browser.show() # Start the event loop print(f"inside saml_authentication - starting app event loop") app.exec_() # https://stackoverflow.com/questions/55819330/catching-exceptions-raised-in-qapplication app.quit() my_saml_token = browser.saml_token # https://stackoverflow.com/questions/40704725/qwebengineview-not-closing-properly-in-some-cases del app del browser print(f"inside saml_authentication - returning our saml_token") # return our saml_token (will be blank if we didn't get one) return my_saml_token except Exception: # just trying to catch anything at this point traceback.print_exec() return None # Driver Code PVWA_ADDRESS = "https://passwordvault.acme.net" # This first function call works great, and returns my SAML Token token = saml_authentication(PVWA_ADDRESS) # This second function call hangs and exits the console, no errors/traceback printed token = saml_authentication(PVWA_ADDRESS) Here are the print statements from a successful run, and a failed run: SUCCESSFUL RUN Getting initial IDP URL from Password Vault Initial IDP URL returned as: 'https://login.microsoftonline.com/abcdefg123456/saml2?SAMLRequest=WXYZ9876' Looking in session cookies for the 'CA88888' cookie Found the 'CA88888' cookie inside saml_authentication - starting QApplication inside saml_authentication - starting SAMLBrowser inside SAMLBrowser.navigate_to_idp - running setUrl with idp_url 'https://login.microsoftonline.com/abcdefg123456/saml2?SAMLRequest=WXYZ9876' inside SAMLBrowser.navigate_to_idp - setUrl complete inside saml_authentication - running browser.show() inside saml_authentication - starting app event loop inside SAMLBrowser.check_for_saml_response - calling process_page inside SAMLBrowser.check_for_saml_response - call to process_page complete inside SAMLBrowser.process_page inside SAMLBrowser.check_for_saml_response - calling process_page inside SAMLBrowser.check_for_saml_response - call to process_page complete inside SAMLBrowser.process_page inside SAMLBrowser.check_for_saml_response - calling process_page inside SAMLBrowser.check_for_saml_response - call to process_page complete inside SAMLBrowser.process_page inside SAMLBrowser.check_for_saml_response - calling process_page inside SAMLBrowser.check_for_saml_response - call to process_page complete inside SAMLBrowser.process_page inside SAMLBrowser.process_page - FOUND SAMLRESPONSE!!! inside SAMLBrowser.process_page - runJavaScript() complete inside SAMLBrowser.handle_saml_response inside SAMLBrowser.handle_saml_response - FOUND SAMLRESPONSE!!! Inside saml_authentication.on_saml_response Adding 'CA88888' cookie to our session cookies, in preparation for our final call to PWV to get our token final call to PWV made inside SAMLBrowser.handle_saml_response - set saml_token successfully. inside SAMLBrowser.handle_saml_response - calling .close() inside SAMLBrowser.handle_saml_response - .close() called successfully inside SAMLBrowser.check_for_saml_response - calling process_page inside SAMLBrowser.check_for_saml_response - call to process_page complete inside SAMLBrowser.process_page inside saml_authentication - returning our saml_token FAILED RUN (when I run the function the 2nd time and it hangs and exits the console) Getting initial IDP URL from Password Vault Initial IDP URL returned as: 'https://login.microsoftonline.com/abcdefg123456/saml2?SAMLRequest=WXYZ9876' Looking in session cookies for the 'CA88888' cookie Found the 'CA88888' cookie inside saml_authentication - starting QApplication inside saml_authentication - starting SAMLBrowser inside SAMLBrowser.navigate_to_idp - running setUrl with idp_url 'https://login.microsoftonline.com/abcdefg123456/saml2?SAMLRequest=WXYZ9876' Continue reading...