Part 2 of 7 · Visa Manager App

Putting Live Websites Inside a Desktop App

The whole cockpit rests on one capability, a real web page living inside my desktop window. PyQt5 delivers it through QWebEngineView, a widget that is a complete Chromium browser engine, and this post is the embedding itself, the minimal working core, the install quirk that stops most people first, and the wiring that makes an embedded page feel native.

The install quirk first, because it is where half the attempts die. The web engine is not inside the base PyQt5 package, it is its own install, and forgetting that produces an import error that looks like a broken PyQt:

# both are required; the second is the one people miss
# pip install PyQt5 PyQtWebEngine

from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QUrl

With the imports satisfied, the minimal embedded browser is honestly this small:

class BrowserWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('UAE Visa Manager')
        self.resize(1280, 860)

        self.view = QWebEngineView()
        self.view.load(QUrl('https://cheapuaevisa.com/wp-admin'))
        self.setCentralWidget(self.view)

app = QApplication([])
win = BrowserWindow()
win.show()
app.exec_()

Run it and the WordPress login renders inside a desktop window, full Chromium, JavaScript, forms, everything, because it is not a rendering of the page, it is the same engine Chrome uses, drawing into a widget. The step from toy to tool is wiring the view’s signals, the events the engine emits, into interface behaviour, a loading indicator tied to loadStarted and loadFinished, and a refresh action per view:

self.view.loadStarted.connect(lambda: self.status.setText('Loading...'))
self.view.loadFinished.connect(lambda ok: self.status.setText('' if ok else 'Failed to load'))

refresh = QPushButton('Refresh')
refresh.clicked.connect(self.view.reload)

Those few connections are what separate an embedded page from a trapped one, the user can see state and recover from a bad load without the app pretending nothing happened. One honest note on weight, each QWebEngineView is a browser engine, with a browser engine’s memory appetite, four views is a reasonable cockpit, forty would be a mistake, the architecture suits a curated handful of tools, which is exactly the use case. The subclass in this example matters for one more reason, the real app extends it, CustomWebView in later posts carries the password manager hooks, and starting from a subclass means those features have a home waiting.

A few things people ask me about this

Why does importing QtWebEngineWidgets fail when PyQt5 is installed? The web engine ships as its own package. pip install PyQtWebEngine beside PyQt5 and the import resolves.

Can the embedded page tell it is inside my app? It runs like any Chromium, sites work normally. You control it from outside, URLs, reloads, and, as later posts show, its cookie storage and page scripts.

Next

One embedded page is a demo, the cockpit needs four, sharing one login state so signing into WordPress once covers every tab, across restarts. Sessions and profiles are the next post.

Leave a Reply

Your email address will not be published. Required fields are marked *