Getting started¶
Install¶
The core install depends on comtypes alone. The heavier pieces are optional:
pip install 'wintegrate[video]' # screen recording + failure screenshots
pip install 'wintegrate[desktop]' # virtual desktop clean-room isolation
pip install 'wintegrate[html]' # pytest --html report with session screenshots, steps and video
pip install 'wintegrate[all]'
Using a feature without its extra raises an error naming the extra to install, so a missing dependency never turns into a silently skipped diagnostic.
Windows only, but importable everywhere
The package imports on macOS and Linux — so cross-platform tooling, linting
and env checks work — while every Win32 and UIA call raises a clear
unsupported-platform error rather than failing obscurely.
A first script¶
from pathlib import Path
from wintegrate import NOTEPAD, Session, SessionConfig
config = SessionConfig(artifact_dir=Path("ci-artifacts"), record_video=True)
with Session(config) as session:
with session.app(NOTEPAD) as app:
editor = app.find_text_input()
editor.type_verified(
"hello from CI\n",
expected_line_count_delta=1,
verify_contains="hello from CI",
)
assert "hello from CI" in editor.get_value()
On exit the session writes window_census.json, session_events.json, the
recording, and — if the block raised — failure_screenshot.png.
Runnable versions live in
examples/.
Finding things¶
Prefer identities that do not change with the UI language.
from wintegrate import Window
# Criteria are ANDed: this is the Settings dialog, not the first #32770 around.
dialog = Window.find(title_pattern="Settings", class_name="#32770")
# pid= is the reliable way to reach a window whose title is localized,
# empty, or shared with another application.
win = Window.find(class_name="Notepad", pid=proc.pid)
root = dialog.re_resolve_element() # always re-resolve; COM wrappers go stale
edit = root.find_descendant(automation_id="1002")
maybe = root.find_descendant(automation_id="9999", required=False) # None, no raise
Typing, and why there are two ways¶
editor.type_verified("abc", verify_contains="abc") # Unicode injection
editor.send_physical_keys("abc") # scan codes
editor.send_keys("{HOME}+{END}{DELETE}") # named keys and modifiers
type_verified injects Unicode codepoints. They reach the control directly and
never pass through an IME, so composition never starts. That is the right
call when you just need characters in a field.
send_physical_keys delivers scan codes, which is the only input an IME
intercepts. Reach for it when the IME itself is what you are testing.
Collapse the selection before a destructive key
Focusing a classic Win32 EDIT through UIA selects its entire contents;
arriving by click places the caret instead. send_keys focuses first and does
both, so whether a bare {BACKSPACE} clears the field or deletes one
character is timing-dependent. Send {HOME} or {END} first and the question
stops mattering.
Screenshots¶
session.capture_screenshot("after-login") # whole virtual desktop
session.capture_screenshot("dialog", window=dialog) # that window only
window.capture("window.png")
element.capture("control.png")
A window capture uses PrintWindow, so it includes the parts of the window that
other windows are covering. On CI the thing on top is frequently the popup that
broke the run, and a cropped screenshot would show the intruder instead of the
window you were driving.
A failure screenshot is still written automatically when the Session block
raises — but only then, which is why an explicit call is worth having: by the time
something fails, the state that explains it is often gone.
Dialog controls¶
check = root.find_descendant(automation_id="1011")
check.set_toggle_verified(True) # Toggle() only cycles; this drives to a state
combo = root.find_descendant(automation_id="1003")
combo.expand_verified(True)
combo.find_all(control_type_id=50007)[1].select_verified()
combo.get_value() # a Win32 combo reports selection as its value
items = root.find_descendant(automation_id="1001").children()
items[1].select_verified()