Desktop Driver (Windows Native App Automation)
Overview
VerisoftDesktopDriver and DesktopBasePage provide automation for Windows native (desktop) applications via appium-wincore-driver. This is the desktop analog of the mobile/web drivers in the framework — page objects extend DesktopBasePage instead of WebBasePage / MobileBasePage.
You can find a detailed documentation for the driver in here
You’ll also find a general description in here
Requirements:
-
Appium server with appium-wincore-driver 3.0.0 or newer installed:
appium driver install appium-wincore-driver -
Capabilities must include:
platformName = "Windows"automationName = "Wincore"
-
macOS is not supported.
Page Object HierarchyPage Object Hierarchy
BasePage
├── WebBasePage
├── WebBasePageJS
├── MobileBasePage
└── DesktopBasePage ← Windows native apps
Supported Functions
UI Automation Patterns (element-based)
| Method | Description |
|---|---|
windowsInvoke(element) |
Invokes InvokePattern (buttons, menu items, hyperlinks) |
windowsExpand(element) |
Expands a collapsible element (tree nodes, combo boxes, expanders) |
windowsCollapse(element) |
Collapses an expanded element |
windowsToggle(element) |
Toggles a checkable element (checkboxes, toggle buttons) |
windowsSelect(element) |
Selects an item via SelectionItemPattern |
windowsAddToSelection(element) |
Adds an item to a multi-select list selection |
windowsRemoveFromSelection(element) |
Removes an item from a multi-select list selection |
windowsScrollIntoView(element) |
Scrolls the element into view via ScrollItemPattern |
windowsSetFocus(element) |
Moves keyboard focus to the element |
windowsGetValue(element) |
Returns element value via ValuePattern (null if unsupported) |
windowsSetValue(element, text) |
Sets element value via ValuePattern |
windowsGetSelectedItem(element) |
Returns currently selected child item (null if none) |
windowsGetAllSelectedItems(element) |
Returns all selected child items of a multi-select container |
windowsIsMultipleSelection(element) |
Returns whether the container supports multiple selection |
windowsSelectFromCombobox(element, value) |
Opens the combo box, selects the item whose name exactly matches value, and closes the list again |
Window Management
| Method | Description |
|---|---|
windowsMaximizeWindow(element) |
Maximizes the window containing the element via WindowPattern |
windowsMinimizeWindow(element) |
Minimizes the window |
windowsRestoreWindow(element) |
Restores window to previous size |
windowsCloseWindow(element) |
Closes the window via WindowPattern |
windowsSwitchToWindowByTitle(title) |
Switches session root to first window whose title contains the string |
windowsSwitchToWindowByTitle(title, exact) |
Switches to window whose title matches (exact=true) or contains (exact=false) the string |
windowsWaitForWindowByTitle(title) |
Waits up to 5s for a window (contains match), then switches |
windowsWaitForWindowByTitle(title, exact) |
Waits up to 5s for a window (exact/contains), then switches |
windowsWaitForWindowByTitle(title, timeOut) |
Waits up to timeOut seconds (contains match); throws RuntimeException if not found |
windowsWaitForWindowByTitle(title, exact, timeOut) |
Waits up to timeOut seconds (exact/contains); throws RuntimeException if not found |
windowsGetWindowElement() |
Returns the root window element of the current session |
windowsGetWindows() |
Returns list of maps for all open top-level windows: handle, title, className |
App Lifecycle
| Method | Description |
|---|---|
windowsLaunchApp() |
Re-launches the AUT using original session capabilities |
windowsCloseApp() |
Closes the AUT |
Pointer Input
| Method | Description |
|---|---|
windowsClick(element) |
Left-clicks centered on the element |
windowsClick(element, button) |
Clicks element with specified button: "left" (default), "right", "middle", "back", "forward" |
windowsClick(x, y) |
Left-clicks at absolute screen coordinates |
windowsClick(WindowsClickOptions) |
Full options builder — element or coordinates, button, modifier keys, repeat count, hold duration |
windowsHover(element) |
Moves pointer to center of element |
windowsHover(WindowsHoverOptions) |
Full options builder — start/end element or coordinates, modifier keys, duration |
windowsScroll(element, deltaX, deltaY) |
Scrolls mouse wheel over an element |
windowsScroll(x, y, deltaX, deltaY) |
Scrolls mouse wheel at absolute coordinates |
windowsScroll(WindowsScrollOptions) |
Full options builder — element or coordinates, delta, modifier keys |
windowsClickAndDrag(WindowsClickAndDragOptions) |
Drags from one element/position to another |
windowsKeys(CharSequence... keys) |
Sends keys through the windows: keys command (native input by Windows virtual-key code). It takes the same arguments as WebElement.sendKeys: text plus Selenium Keys constants. A chord such as Keys.chord(Keys.CONTROL, "a") holds the modifier. Keys go to the focused window, not to an element, so focus the target first. Throws IllegalArgumentException if a Keys constant has no virtual-key mapping. |
windowsKeys(WindowsKeysOptions) |
Full builder: pause(ms), type(text), pressKey(Keys / text / vkCode), keyDown(...), keyUp(...), forceUnicode(bool) |
Options builders (WindowsClickOptions, WindowsHoverOptions, WindowsScrollOptions, WindowsClickAndDragOptions, WindowsKeysOptions) are static factory + fluent builders on VerisoftDesktopDriver, e.g.:
getDesktopDriver().windowsClick(
WindowsClickOptions.onElement(myButton)
.button("right")
.modifierKeys(ModifierKey.CTRL, ModifierKey.SHIFT)
.times(2)
.durationMs(300));
WindowsClickOptions also supports interClickDelayMs(ms) (default 100), the delay between clicks when times > 1. WindowsClickAndDragOptions supports button(...) and durationMs(ms) (default 500)
driver.windowsKeys("username", Keys.TAB, "password", Keys.ENTER);
driver.windowsKeys(Keys.chord(Keys.CONTROL, "a"));
driver.windowsKeys(WindowsKeysOptions.create()
.keyDown(Keys.CONTROL).pressKey("a").pressKey("c").keyUp(Keys.CONTROL));
driver.windowsKeys(WindowsKeysOptions.create()
.type("search term").pause(200).pressKey(Keys.ENTER));
Supported modifier keys (ModifierKey enum): SHIFT, CTRL, ALT, WIN.
Scrolling (mouse wheel)
| Method | Description |
|---|---|
scrollByDirection(direction) |
Scrolls the app window one step (3 wheel notches) with a W3C wheel action, centred on the window |
scrollInsideContainer(container, direction) |
Scrolls a container element one step, centred on the container |
scrollInToViewElement(By, direction) |
Scrolls until the target is on screen. Stops after 10 attempts, or earlier when the page stops changing |
scrollInToViewElement(By, direction, maxScrolls) |
Same, with your own attempt limit |
scrollInToViewElement(container, By, direction, maxScrolls) |
Scrolls inside a container. Stops when the container’s first visible child stops moving |
The direction works the same way as in the swipe methods: DOWN moves the content down, which reveals what is above it. UP reveals what is below.
If the target never comes on screen, the method throws ElementNotInteractableException.
The scrollInToViewElement variants work only in the native context.
Clipboard
| Method | Description |
|---|---|
windowsGetClipboard() |
Returns current plain-text clipboard content (null if empty) |
windowsGetClipboard(contentType) |
Returns clipboard content as base64 string; contentType = "plaintext" or "image" |
windowsSetClipboard(content) |
Writes plain-text content to clipboard |
windowsSetClipboard(data, contentType) |
Writes raw bytes to clipboard (e.g. PNG image bytes) with given content type |
Browser Address Bar (best-effort)
| Method | Description |
|---|---|
windowsGetUrl() |
Reads a browser's address bar via CTRL+L → CTRL+C → clipboard read |
windowsSetUrl(url) |
Sets a browser's address bar via CTRL+L → type → ENTER |
Caveat: These go through keyboard shortcuts, not a native extension command — there is no reliable way to detect "is this actually a browser window." Only meaningful when the focused top-level window is a browser exposing a standard address bar bound to
CTRL+L.
System / Environment
| Method | Description |
|---|---|
executePowerShell(script) |
Executes a PowerShell script via appium-desktop-driver's powerShell extension; returns output as String |
windowsGetDeviceTime() |
Returns current device/system time as ISO-8601 string |
windowsGetDpiScale() |
Returns OS display scale factor (1.0 @ 100%, 1.5 @ 150%, etc.) — use to convert logical UIAutomation coords to physical pixel coords for windowsClick(x, y) and similar |
windowsGetMonitors() |
Returns list of maps describing connected monitors: left, top, right, bottom, width, height, isPrimary |
File System
| Method | Description |
|---|---|
windowsDeleteFile(remotePath) |
Deletes a file on the remote machine. Requires MODIFY_FS_FEATURE flag enabled on Appium server |
windowsDeleteFolder(remotePath) |
Deletes a folder on the remote machine. Requires MODIFY_FS_FEATURE flag enabled on Appium server |
Screen Recording
| Method | Description |
|---|---|
windowsStartRecordingScreen() |
Starts screen recording via FFmpeg. Requires ffmpegExecutablePath capability or FFmpeg on system PATH |
windowsStopRecordingScreen() |
Stops recording, returns video as base64-encoded MP4 string |
Vision-Based Element Finding
| Method | Description |
|---|---|
windowsFindByVision(prompt, model) |
Finds a screen location using LLM-based vision matching. Returns VisionMatch(x, y, label, annotatedImageBase64) |
windowsFindByVision(prompt, model, includeAnnotatedImage) |
Same, but when the flag is true it also returns a base64 PNG of the match, annotated, for debugging |
model is the vision LLM id (for example claude-sonnet-4-6). See the appium-wincore-driver API.md for the supported prefixes and the API-key environment variables.
Standard Selenium (partially supported)
| Area | Support |
|---|---|
switchTo().window(handle), switchTo().activeElement() |
Supported |
manage().timeouts(), manage().window() |
Supported |
waitForElement(locator) |
Supported — uses configured selenium.wait.timeout, tuned for native control render latency |
waitForFocus(element) (DesktopBasePage) |
Waits until the element has keyboard focus (UI Automation HasKeyboardFocus). Uses selenium.wait.timeout |
Context Switching (WebView Support)
VerisoftDesktopDriver implements SupportsContextSwitching, so context(name), getContext(), and getContextHandles() are supported. This matters for apps that embed a web renderer (Electron, CEF-based apps, etc.) — the driver can enumerate and switch into a WEBVIEW_* context the same way VerisoftMobileDriver does for hybrid mobile apps. WEBVIEW_* contexts require the webviewEnabled capability.
Once switched into a WEBVIEW_* context, appium-wincore-driver transparently proxies most standard WebDriver commands to a real chromedriver/edgedriver session attached via CDP. Concretely, the following are supported while a WEBVIEW_* context is active (and will fail/throw in the default native context, since appium-desktop-driver has no native handler for them):
get(url)navigate()— back/forward/refresh/toswitchTo().frame(...)(all overloads),switchTo().parentFrame(),switchTo().defaultContent()switchTo().newWindow(typeHint)— note: a window opened this way isn't auto-tracked as the current context; callgetContextHandles()/context(name)again to pick it upswitchTo().alert()manage().addCookie(),deleteCookieNamed(),deleteCookie(),deleteAllCookies(),getCookies(),getCookieNamed()print(printOptions)addVirtualAuthenticator(),removeVirtualAuthenticator()executeScript(String, Object...)(plain JS execution)getCurrentUrl(()swipe*touch helpers and W3C wheel scrolling
windows:-prefixed extension commands and executePowerShell() always target the native UIA layer regardless of the active context.
Unsupported Operations
Calling any of these throws UnsupportedOperationException, regardless of active context — appium-wincore-driver has no implementation for them at all, native or WEBVIEW_*:
pin(script),unpin(key),getPinnedScripts()— BiDi script pinning; the plugin has no BiDi bridging code in either contextexecuteScript(ScriptKey, args...)— same BiDi gap as above (note: this only blocks theScriptKey-pinned-script overload; plainexecuteScript(String, Object...)works fine in aWEBVIEW_*context, see above)
Touch/gesture (mobile-specific)
swipe* helpers fail in the native context, because appium-wincore-driver synthesizes no touch input. They work in a WEBVIEW_* context, where the call is proxied to chromedriver/edgedriver. On native UI, use windowsClick, windowsHover, windowsScroll, windowsClickAndDrag, scrollByDirection, scrollInsideContainer or scrollInToViewElement instead
Notes
- Coordinate-based methods (
windowsClick(x, y),windowsScroll(x, y, ...), etc.) expect physical pixel coordinates. If working from logical UIAutomation coordinates, convert usingwindowsGetDpiScale()first. windowsGetUrl()/windowsSetUrl()have no verification that the focused window is actually a browser — use with that caveat in mind.
Example
package co.verisoft.examples.drivers;
import co.verisoft.fw.selenium.drivers.VerisoftDesktopDriver;
import co.verisoft.fw.selenium.drivers.factory.DriverCapabilities;
import co.verisoft.fw.selenium.drivers.factory.DriverUrl;
import co.verisoft.fw.selenium.junit.extensions.DriverInjectionExtension;
import co.verisoft.fw.selenium.junit.extensions.ScreenShotExtension;
import co.verisoft.fw.selenium.junit.extensions.SeleniumLogExtesion;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Self-contained example of a VerisoftDesktopDriver test.
* Everything a dev needs is in this one file, split into three parts
* that normally live in three separate files in a real project:
*
* 1. CapabilitiesConfig - a Spring @Configuration bean per named capability set
* (normally: src/main/java/.../dependencyinjection/DesktopCapabilitiesConfig.java)
* 2. BaseTest - registers the JUnit extensions that make driver
* injection, screenshots-on-failure and log capture work
* (normally: src/test/java/.../DriverBaseTest.java, extended by every UI test class)
* 3. ExampleTest - the actual test, extends BaseTest and asks for a
* VerisoftDesktopDriver via @DriverCapabilities/@DriverUrl
*
* Requires an Appium server running on localhost:4723 with appium-wincore-driver installed.
*/
class VerisoftDesktopDriverExampleTest {
@Configuration
static class CapabilitiesConfig {
@Bean("windows-notepad")
public DesiredCapabilities windowsNotepad() {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Windows");
caps.setCapability("appium:automationName", "Wincore");
caps.setCapability("appium:app", "C:/Windows/System32/notepad.exe");
caps.setCapability("ms:waitForAppLaunch", 5);
return caps;
}
}
@ExtendWith({DriverInjectionExtension.class, ScreenShotExtension.class, SeleniumLogExtesion.class})
static class BaseTest {
// Body stays empty here: the extensions above handle driver injection,
// screenshots and log capture on their own. This is the place to add
// shared @BeforeAll / @AfterAll hooks common to all UI tests, if needed.
}
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = CapabilitiesConfig.class)
static class ExampleTest extends BaseTest {
@Test
void shouldTypeTextIntoNotepad(
@DriverCapabilities("windows-notepad")
@DriverUrl("http://127.0.0.1:4723")
VerisoftDesktopDriver driver) {
WebElement editArea = driver.findElement(By.xpath("//Document"));
editArea.sendKeys("hello world");
assertEquals("hello world", editArea.getText());
}
}
}