It's a little scattered, but there are a bunch of caching settings to take care of. Regarding HttpCache:
You might end up with something like this (No need to put raw enum values):
profile = self.web_view.page().profile()
profile.setHttpCacheType(QWebEngineProfile.NoCache)
profile.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
profile.clearHttpCache() #These are a bit extra, but better to be safe than sorry?
profile.cookieStore().deleteAllCookies()
While the above is still good practice, your seems to actually come from timing issues. Because the initial caching recommendations didn't work and QTextBrowser works fine while QWebEngine fails for your purposes, my best guess is that QWebEngineView.setHtml() is the root of your issues.
When setHtml() is used on a QWebEngineView, it is asynchronous. According to the documentation, "The HTML document is loaded immediately, whereas external objects are loaded asynchronously." This means that even though the function is called, the page might not be able to render content, even if refresh() is called. Also note that loadFinished(), the signal called when the load is finished, will trigger with success = false if larger than 2MB.
Confusion can occur with setHtml() because it is not asynchronous for QTextBrowser.
Here are a few possible solutions you can try to solve the timing issue:
First
Update to from PyQt5 to PyQt6. Note that support for 5 is ending at the end of May. Qt6 has callbacks and additional techniques that are helpful.
Second
When using a QWebEngineView, you can try to listen for when setHtml() has finished, but it depends on how setHtml() is used. There are conflicting sources online that you can only listen successfully to pages, not the view, but I was able to do so successfully when testing:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWebEngineWidgets import QWebEngineView
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
view = QWebEngineView()
view.loadFinished.connect(self.no_kludge_finished)
html = """
<html><body><p>Yay</></body></html>
"""
view.setHtml(html)
self.setCentralWidget(view)
def no_kludge_finished(self, ok):
print("Load finished:", ok)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
They key lies in:
view.loadFinished.connect(self.no_kludge_finished) and def no_kludge_finished(self, ok):. In my testing, there was a perfect success rate of the function being called on load.
Third
You can use a URL. As mentioned during the setHtml() explanation, loadFinished() maxes out at 2MB; this is a result of how setHtml sends the actual data– it simply encodes it and ships it as data with text/html. We can do the same.
I played around to create an example that shows style, JS, and HTML:
import sys
import base64
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QUrl
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
view = QWebEngineView()
view.loadFinished.connect(self.very_elegant_finish)
html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Look at the stuff it does no problem</title>
<style>
#tog {
background-color: red;
color: white;
border: none;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
border-radius: 5px;
transition: background-color 0.3s;
}
#tog.blue {
background-color: blue;
}
</style>
</head>
<body>
<button id="tog">color toggle</button>
<script>
const button = document.getElementById('tog');
button.addEventListener('click', () => {
button.classList.toggle('blue');
});
</script>
</body>
</html>
"""
encoded = base64.b64encode(html.encode("utf-8")).decode("utf-8")
data_url = QUrl(f"data:text/html;base64,{encoded}")#the setHtml() function does the same in how it sends data
view.setUrl(data_url)# setURL() is a more reliable trigger for loadFinished, which is a benefit over setHTML() on the async side
self.setCentralWidget(view)
def very_elegant_finish(self, ok):
print("Load finished:", ok)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
view.setUrl() is guaranteed to trigger loadFinished(), so you will know exactly when to execute your next task.