Running Windows desktop tests from Linux with disposable VMs
Scaling Windows Desktop Automation via Disposable VMs
By Jaden Lemmon | October 14, 2026
For a long time, automating Windows desktop applications was a major pain point for our clients. The reason is simple: almost no one possesses the infrastructure required to parallelize thousands of Windows test runs every month.
The Infrastructure Gap: Web vs. Windows
Testing for the web is streamlined. You spin up a headless browser in a Linux container, it costs nearly nothing, and you delete it the moment the test concludes. Windows is a different beast entirely.
| Feature | Web Testing (Linux) | Windows Desktop Testing |
|---|---|---|
| Environment | Lightweight Container | Full Virtual Machine (VM) |
| Startup Time | Seconds | Minutes |
| State | Ephemeral/Clean | Persistent (Registry, Profiles) |
| UI Access | DOM/CSS Selectors | UI Automation (UIA) |
| Cost | per run |
The challenges are systemic:
Windows ContainersThey lack the graphical desktop required for most apps.- UIA Constraints: Windows UI Automation only functions within an active, authenticated desktop session.
- State Pollution: Registry keys, cached logins, and zombie processes linger between tests.
- Setup Friction: Dealing with admin prompts, installers, and first-run wizards is a nightmare.
"A remote Windows machine provides absolutely nothing unless you explicitly build it."
Since most CI/CD pipelines and Kubernetes clusters are optimized for Linux, we had to pivot our strategy.
The Solution: The "Mobile Device" Model
Instead of treating Windows machines as persistent servers, we decided to treat them like mobile phones: a device is claimed by one test, used once, and then discarded.
The High-Level Architecture
We adapted our existing mobile testing framework. In that model, the test logic cannot run on the device itself. By modeling a Windows VM as a "device" within Kubernetes, a test can request a DeviceClaim, receive connection details, execute commands via WebdriverIO and Appium, and then release the VM.
Key Extensions for Windows:
- External Hosting: VMs live on Google Compute Engine (GCE), outside the K8s cluster.
- Multi-Protocol Access: Claims return multiple addresses, including the Chrome DevTools Protocol (CDP) for browser-based elements.
System Components
The system consists of three primary pillars:
- The Controller (K8s): Manages the GCE VM pool and handles distribution.
- The Windows VM (GCE):
- Runs the target application.
- Hosts automation tools and a Go-based desktop agent.
- Uses WebRTC to stream the screen for live monitoring and recording.
- The Linux Runner Pod: Executes the actual test code using
@qawolf/flows/windows.
The Execution Lifecycle
When a user triggers a test run, the following sequence occurs:
Detailed Step-by-Step:
- Request: The Linux pod asks the scheduler for a device.
- Assignment: The scheduler picks a "warm" (already booted and logged-in) VM from GCE.
- Connection: The runner receives the connection strings for:
CDPPlaywright (for browser content).Appium/WinAppDriverUIA (for native Windows content).WebRTCVisual stream.
- Deployment: The
startApp()command triggers the VM to download the app via a signed URL and install it. - Execution: Commands are sent; the VM streams video back. If the VM stops sending heartbeats for 60 seconds, the scheduler reclaims it.
- Cleanup: The claim is released, the VM is terminated, and a fresh one is added to the pool.
Managing the Fleet with Kubernetes
To manage these VMs at scale, we integrated them into our existing emulator scheduler using three custom Kubernetes resources:
WindowsVMTemplate: Defines the "blueprint" (machine image, CPU/RAM, config).WindowsVMPool: Defines the desired count of ready-to-use VMs for a specific template.DeviceClaim: The request object used by a test to acquire a VM.
Example Configuration Logic:
apiVersion: qawolf.io/v1
kind: WindowsVMPool
metadata:
name: enterprise-app-pool
spec:
template: windows-win11-standard
minReady: 5
maxReady: 20
By using these resources, our engineers can use standard kubectl commands to inspect the health of the Windows fleet. If the scheduler restarts, it simply reads these records and resumes management without losing track of the VMs.
Final Checklist for Implementation:
- Provision GCE Project
- Configure Go Desktop Agent
- Set up WinAppDriver on Base Image
- Implement WebRTC Streaming
- Integrate with K8s Custom Resource Definitions (CRDs)