Metadata-Version: 2.5
Name: wintegrate
Version: 0.5.9
Summary: Integrate Windows desktop apps into modern CI: UI automation for unattended headless runners.
Project-URL: Homepage, https://mangokingtw.github.io/wintegrate/
Project-URL: Documentation, https://mangokingtw.github.io/wintegrate/
Project-URL: Repository, https://github.com/mangokingTW/wintegrate
Project-URL: Issues, https://github.com/mangokingTW/wintegrate/issues
Project-URL: Changelog, https://github.com/mangokingTW/wintegrate/releases
Author-email: Mango Yen <mango.yen.tw@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: arm64,ci,gui-testing,testing,ui-automation,uia,windows
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Win32 (MS Windows)
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.11
Requires-Dist: comtypes>=1.4.16
Provides-Extra: all
Requires-Dist: av>=15.0.0; extra == 'all'
Requires-Dist: pillow>=12.3.0; extra == 'all'
Requires-Dist: pyvda>=0.6.0; extra == 'all'
Provides-Extra: desktop
Requires-Dist: pyvda>=0.6.0; extra == 'desktop'
Provides-Extra: dev
Requires-Dist: av>=15.0.0; extra == 'dev'
Requires-Dist: hypothesis>=6.140.3; extra == 'dev'
Requires-Dist: pillow>=12.3.0; extra == 'dev'
Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
Requires-Dist: pytest>=9.1.1; extra == 'dev'
Requires-Dist: pyvda>=0.6.0; extra == 'dev'
Requires-Dist: ruff>=0.16.5; extra == 'dev'
Provides-Extra: video
Requires-Dist: av>=15.0.0; extra == 'video'
Requires-Dist: pillow>=12.3.0; extra == 'video'
Description-Content-Type: text/markdown

# wintegrate

**Integrate Windows desktop apps into modern CI.**

`wintegrate` (**Win**dows + **integrate**) is a Python library built to seamlessly integrate Windows GUI testing into unattended, headless CI pipelines — where no human is watching and the only evidence of what happened is whatever the run left behind.

[![CI UI Automation Tests](https://github.com/mangokingTW/wintegrate/actions/workflows/ci.yml/badge.svg)](https://github.com/mangokingTW/wintegrate/actions/workflows/ci.yml)
[![Python 3.11 | 3.12 | 3.13 | 3.14](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)](https://pypi.org/project/wintegrate/)
[![Architecture x64 | ARM64](https://img.shields.io/badge/architecture-x64%20%7C%20ARM64-brightgreen)](https://github.com/mangokingTW/wintegrate)
[![Docs](https://img.shields.io/badge/docs-mangokingtw.github.io-blue)](https://mangokingtw.github.io/wintegrate/)
[![Code Style: Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
[![PyPI](https://img.shields.io/pypi/v/wintegrate)](https://pypi.org/project/wintegrate/)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/mangokingTW/wintegrate/badge)](https://scorecard.dev/viewer/?uri=github.com/mangokingTW/wintegrate)
[![CodeQL](https://github.com/mangokingTW/wintegrate/actions/workflows/codeql.yml/badge.svg)](https://github.com/mangokingTW/wintegrate/actions/workflows/codeql.yml)

---

## What a CI run actually looks like

Every CI run records itself. Below is the **complete test suite** on both architectures — not a highlight reel, not a staged demo: the entire run, unedited, at 8× speed. Nothing was cropped or cleaned up, so you are also seeing the runner's own console spam and whatever Windows decided to pop up that morning. That is the point.

Each frame names the test that produced it, in the bottom-left corner, so a moment worth looking at can be traced back to the test that caused it.

| `windows-latest` (x64) — 115s | `windows-11-arm` (ARM64) — 139s |
|---|---|
| ![Full test suite on x64](assets/full-suite-x64.gif) | ![Full test suite on ARM64](assets/full-suite-arm64.gif) |

Watch the same scenario on both. In these two runs Notepad became discoverable in 0.10s on x64 and 0.36s on ARM64 — but the same measurement on a cold ARM64 runner has come back at **17.18s**, against 0.10s on x64. That spread is the point: a discovery timeout tuned on the left-hand machine fails intermittently on the right-hand one, and the failure reads like a flaky test rather than a cold start. This is the class of problem `wintegrate` exists to make visible, and the reason the timings are written to `session_events.json` on every run rather than quoted once in a README.

The full-resolution recordings are `recording-artifacts/full-suite-<arch>.mp4`, attached to the artifacts of [every CI run](https://github.com/mangokingTW/wintegrate/actions/workflows/ci.yml), along with the window census, the event timeline, and the failure screenshots.

---

## Why wintegrate?

Most Windows GUI automation frameworks (e.g. `pywinauto`, `pyautogui`) are built for interactive desktop use on developer workstations. In unattended CI runners (`windows-latest`, `windows-11-arm`), standard interactive assumptions break down:

- **Fail-safes panic in CI**: Moving the cursor into a corner panics interactive libraries and halts test runs.
- **Stale COM wrappers**: Reusing resolved UI element references across seconds causes silent `COMError` failures.
- **Launcher PID != Window PID**: Modern packaged Windows apps (like Notepad, Terminal) launch a starter shim process that does not own the visible HWND.
- **Memory Exhaustion**: Collecting frames in memory before writing exhausts RAM — a two-minute capture at 1024x768 is over 10 GB of raw pixels.
- **ARM64 Scancode & Input Drops**: DirectX scan-code simulation fails on Windows ARM64 virtualization; keypresses are dropped without verification.
- **Multi-Window Discovery Collision**: Launching multiple instances of identical apps causes discovery diffs to select the existing instance rather than the new one.
- **Unverified fire-and-forget**: Typing without post-condition assertions hides silent failures (e.g. counting `\n` while Notepad returns `\r\n` or `\r`).

`wintegrate` is engineered specifically for **unattended CI reliability** across both **x64 and ARM64 Windows**.

---

## Core Features & Architecture

### 1. Verified Actions & Post-Conditions
Every UI interaction confirms its post-condition (line count deltas, buffer content, focus transitions) before returning:
```python
editor.type_verified(
    "Hello from CI\nSecond line\n",
    expected_line_count_delta=2,
    verify_contains="Hello from CI\nSecond line",
    delay_per_char=0.03,
)
```

### 2. Universal x64 & ARM64 Native Unicode Input
Replaced DirectX scan-code simulation with native Win32 `SendInput` utilizing `KEYEVENTF_UNICODE`. Seamlessly injects special characters, linebreaks, and multibyte Unicode across both Intel/AMD x64 and Windows 11 ARM64 virtualization runners.

### 3. Multi-Window Isolation & Discovery Exclusion
Supports launching and discovering multiple concurrent instances of the same application without PID/HWND collisions:
```python
with session.app(NOTEPAD) as app_a:
    # exclude_hwnds guarantees discovery finds the new window, not the existing one.
    # fresh=False: the leftover sweep would otherwise kill Window A.
    with session.app(NOTEPAD, fresh=False, exclude_hwnds={app_a.window.hwnd}) as app_b:
        app_a.find_text_input().type_verified("Window A\n", expected_line_count_delta=1)
        app_b.find_text_input().type_verified("Window B\n", expected_line_count_delta=1)
```

### 4. Non-Invasive Thread Input Focus (`AttachThreadInput`)
Switches foreground focus cleanly using `AttachThreadInput` synchronization between the automation thread and the target window thread. Grants activation authority without invasive keystrokes or Z-order corruption.

### 5. Windows 11 Virtual Desktop Clean-Room Isolation (`pyvda`)
Supports dynamic virtual desktop isolation. When `isolated_virtual_desktop=True`, `wintegrate` creates a clean virtual desktop on the fly, switches to it, executes test operations in isolation away from background noise, and automatically destroys the test desktop on exit.

### 6. Streaming Video & Diagnostic Pipeline
- **Recordings that show pointer, clicks, and keystrokes**: `ContinuousRecorder` draws the cursor into every frame, marks each click with an expanding, fading ring plus a coordinate crosshair, and renders a sleek key visualizer HUD (capsule badge with keystrokes and shortcut chords like `Ctrl + C`, `Win + Alt + Space`). Because everything is drawn into the bitmap after capture rather than via an on-screen window, it has zero desktop intrusion, cannot lose z-order fights to `WS_EX_TOPMOST` windows, and natively decodes Unicode `VK_PACKET` text.
- **Low-Memory Streaming Recorder**: `ContinuousRecorder` encodes in-process through PyAV, which bundles FFmpeg and is the only distribution on PyPI with a `win_arm64` wheel — so recording works out of the box on Windows ARM64 instead of asking the user to install a binary. There is deliberately no external-ffmpeg fallback: it would look like a safety net while making recording depend on something ARM64 users are unlikely to have. Frames carry wall-clock timestamps, so a capture loop that falls behind the nominal frame rate still plays back at real speed.
- **Automatic Failure Artifacts**: Automatically dumps full-screen screenshots and pre/post `window_census.json` diffs on assertion failure.
- **Event Timeline Logging**: Logs action timestamps and targets to human-readable `.log` and structured `.json`.

### 7. Managed App Lifecycle & Locale-Independent Discovery (`session.app`)
Modern Windows apps break naive launch-and-poll automation in specific, repeatable ways.
`session.app()` packages the countermeasures:

```python
from wintegrate import NOTEPAD, Session, SessionConfig

with Session(SessionConfig()) as session:
    with session.app(NOTEPAD) as app:                # cleanup guaranteed, even on failure
        editor = app.find_text_input()               # locale-independent control ladder
        editor.type_verified("hello\n", expected_line_count_delta=1)
```

- **No localized strings**: windows are matched by process image name (`Notepad.exe`),
  window class (`Notepad`), and UIA control types — none of which change with the UI
  language. Title regexes are a last-resort fallback and live once, in the `AppSpec`.
- **Single-instance safety**: Store apps (Notepad) reuse a running instance — a leaked
  instance makes the next launch open a *tab* instead of a new window, and discovery
  times out mysteriously. `fresh="auto"` sweeps leftovers before launching (CI only).
- **Cold-start headroom**: first launches of Store apps regularly exceed 10s on CI
  runners (ARM64 especially); managed launches default to a 30s discovery timeout.
- **No leaks**: the context manager closes the window and kills the process on exit,
  and a timed-out discovery kills its own launcher.

---

## Quickstart

```python
from wintegrate import NOTEPAD, Session, SessionConfig

config = SessionConfig(
    artifact_dir="./ci-artifacts",
    record_video=True,
    fps=30,
)

with Session(config) as session:
    # Managed lifecycle: locale-independent discovery, single-instance safety,
    # generous cold-start timeout, and guaranteed cleanup on exit.
    with session.app(NOTEPAD) as app:
        editor = app.find_text_input()

        # Types hardware keystrokes and verifies line count delta & text content
        editor.type_verified(
            "Hello from CI\nSecond line\n",
            expected_line_count_delta=2,
            verify_contains="Hello from CI\nSecond line",
        )
```

---

## CI/CD Integration

`wintegrate` is purpose-built to eliminate the frustration of debugging headless or unattended Windows GUI test failures on CI runners without RDP access.

### Zero-RDP CI Diagnostics

When a test run completes or encounters an assertion failure in GitHub Actions or Azure Pipelines, `wintegrate` automatically bundles a complete diagnostic package to `artifacts/`:

1. **Full-Motion Video Recording (`.mp4`)**: In-process low-overhead screen capture via PyAV, capturing the exact visual state and timing of the runner.
2. **Window Census Diff (`window_census.json`)**: Pre- and post-test snapshots of all desktop HWNDs, titles, process IDs, and visibility states — immediately revealing rogue popups, leaked instances, or focus-stealing dialogs.
3. **Structured Event Timeline (`session_events.json` / `.log`)**: Millisecond-accurate trace of every window launch, focus transition, and verified keystroke.
4. **Failure Screenshots (`.png`)**: Instant high-resolution captures of the desktop and target window at the exact moment of failure.

### GitHub Actions Workflow Example

Add GUI integration testing with automatic diagnostic artifact collection to your repository in a few lines:

```yaml
name: Windows GUI Integration Tests

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  test-windows:
    strategy:
      matrix:
        os: [ windows-latest, windows-11-arm64 ]
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.13"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install "wintegrate[all]" pytest

      - name: Run Windows Integration Tests
        run: |
          pytest tests/ -v

      - name: Upload Diagnostic Artifacts (Videos & Census)
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: wintegrate-diagnostics-${{ matrix.os }}
          path: ./artifacts/
          if-no-files-found: ignore
```

### Real-World Adoption

See [`ImeModePersistence`](https://github.com/mangokingTW/ImeModePersistence) for a live production example: a Windows system utility that uses `wintegrate` in GitHub Actions CI to drive end-to-end integration tests with live screen recording, focus verification, and IME state assertions across both x64 and ARM64 Windows.

---

Full documentation: **<https://mangokingtw.github.io/wintegrate/>** — including
[what breaks in CI](https://mangokingtw.github.io/wintegrate/pitfalls/), a catalogue of the
failures this library was built against.

---

## Installation

```bash
pip install wintegrate          # core: window/element automation, verified input
uv add wintegrate
```

The core install depends on `comtypes` alone. Two optional extras pull in the
heavier pieces only if you use them:

```bash
pip install 'wintegrate[video]'    # screen recording + failure screenshots (Pillow, PyAV)
pip install 'wintegrate[desktop]'  # virtual desktop clean-room isolation (pyvda)
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.

Everything in `0.5.0` came from driving four real applications — Notepad++,
WinMerge, DB Browser for SQLite and Files — rather than a test app written for
the purpose, and those four now run as a CI release gate on both a client and a
server SKU. Each of them also has a real, already-fixed upstream bug reproduced
against the build that had it. See the [changelog](CHANGELOG.md#050--2026-09-01).

Or install from source with development dependencies:
```bash
git clone https://github.com/mangokingTW/wintegrate.git
cd wintegrate
pip install -e .[dev]
```

Windows only: the package imports on other platforms (so cross-platform tooling and
`env` checks work), but every Win32/UIA call raises a clear unsupported-platform error.

---

## Running Tests

Run full test suite locally:
```powershell
pytest tests/ -v -s
```

Run static code analysis & formatting check:
```powershell
ruff check src/ tests/
```

---

## Verifying a release

Releases are built by a GitHub Actions workflow and published to PyPI through
Trusted Publishing — no API token exists to be stolen. Every file carries PEP 740
attestations (visible on the PyPI page) and GitHub build provenance:

```bash
gh attestation verify wintegrate-<version>-py3-none-any.whl --repo mangokingTW/wintegrate
```

A pass proves the file was built by this repository from a specific commit.

This library synthesizes input, reads window contents, captures the screen, and
can terminate processes — see [SECURITY.md](SECURITY.md) for what that means when
running it unattended, and for how to report a vulnerability privately.

---

## License

MIT License. See [LICENSE](LICENSE) for details.
