End to End Testing Python/Tkinter Desktop Programs
Unit tests prove your logic works in isolation. End to end tests prove the actual application -- the one your users double-click -- behaves. For a Tkinter desktop app, that means driving a real, running process, not a mocked one, and Tk gives you the tools to do that if you stay on Linux or another Unix.
Run under a virtual X display. Xvfb (or Xvnc) gives the real app a real display to draw on without needing a physical screen or a desktop session, which is what makes this practical in CI. The app under test doesn't know the difference.
Drive it with Tk's send command. tk send lets one Tcl/Tk
application evaluate a script inside another, over the same mechanism Tk
uses for inter-application communication. Your test process can reach into
the running app and interact with its actual widgets -- not a proxy, not a
mock, the real thing.
Keep send calls asynchronous, except when you need a value back.
Most interactions -- clicking a button, typing into an entry, opening a
menu -- should be fire-and-forget so your test driver isn't blocked on the
app's event loop. Reserve synchronous send for the cases where you
genuinely need to read a value out of the running app (a label's text, a
variable's current value) before deciding what to do next.
Generate events, don't call methods or invoke directly. Sending a
<Button-1> event to a widget exercises the same code path a real click
does -- binding lookups, focus changes, the works. Calling invoke() on a
button or reaching in to call a handler function directly skips all of
that and can pass even when the actual UI is broken.
A few more points worth building in:
- Target the right window explicitly. Use the app's
wm title(or a dedicated Tk application name) to address it withsend, especially once more than one instance might be running in the same test session. - Wait on state, not on the clock. Poll for a widget to reach an
expected state (or a marker variable to flip) instead of a fixed
sleep. Fixed sleeps are either too short (flaky) or too long (slow), and usually end up being both over the life of a test suite. - Restart the process between scenarios rather than trying to reset it in place. Tk and Tcl don't guarantee a clean slate after arbitrary application code has run; a fresh process does.
- Capture a screenshot on failure. Xvfb still has a framebuffer --
grab it (via
ImageGraborxwd) when an assertion fails, so a CI failure comes with a picture, not just a traceback. - Pin your Tcl/Tk version.
sendbehavior and window addressing have shifted across Tcl/Tk releases and distros; pin what CI uses and match it to what you ship. - Reach for
xdotoolonly for whatsendcan't reach -- native file dialogs and similar OS-level chrome that live outside the Tk application. Prefersendfor everything inside your own app.
The common thread across all of it: exercise the same paths a person would, at the same layer a person would, and let the display be real even if no one's looking at it.