WinJava驱动程序

一个实现W3C WebDriver协议的Windows桌面自动化工具。使用熟悉的Selenium风格的API(类似ChromeDriver)从Java自动化Windows应用程序,但适用于Windows桌面应用程序。
特性
- W3C WebDriver协议 --基于HTTP的标准自动化接口
- Java客户端库 --扩展硒的
RemoteWebDriver实现完全的生态系统兼容性 - Windows用户界面自动化 --将Microsoft的UIAutomation框架用于现代应用程序
- 旧版Win32/VB6支持 --UIA不可见的应用程序的自动回退
- 专注独立互动 --单击、键入和截图,而无需将窗口放在前面
- W3C操作API --通过Selenium的双击、右键单击、拖放、悬停和键盘快捷键
Actions类 - MSFlexGrid单元自动化 --读取和写入VB6 MSFlexGrid控件中的单个单元格
- 检查器GUI --Chrome DevTools风格的元素间谍,具有悬停高亮显示、多定位器面板和VB6标签支持
- 录制与回放 --记录用户交互,生成Java页面对象或JUnit测试代码,回放步骤
- MCP服务器 --AI驱动的桌面自动化,配备智能、令牌高效的工具
- 多种定位策略 --名称、可访问性ID、类名、标记名、xpath
- 名称规范化 —
WinBy.name("Open")自动匹配两者"Open"和"&Open"(Windows快捷键前缀) - 窗户链条 —
switchTo().window(handle)跟踪前一个窗口,switchBack()返回到它 - 系统窗口发现 —
listAllWindows()枚举所有可见窗口;switchToWindowByTitle()按标题片段切换 - 便利API --等待助手、元素索引、子枚举、基于位置的查找、重试和全局
sendKeys() - 截图 --窗口、元素和完整桌面屏幕截图捕获(与z顺序无关)
- Selenium Grid 4集成 --通过网格中继在远程Windows计算机上运行测试
- 黄瓜/BDD就绪 --计算器自动化示例项目
快速开始
先决条件
- Windows 10/11
- Java 21+(客户端)
安装
- 将Java客户端添加到您的项目中——服务器二进制文件是 自动下载 首次运行时:
io.github.glaciousm
winjavadriver-client
1.1.0
基本用法
import io.github.glaciousm.*;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.OutputType;
public class NotepadAutomation {
public static void main(String[] args) {
// Create options to launch Notepad
WinJavaOptions options = new WinJavaOptions()
.setApp("notepad.exe");
// Start driver — auto-discovers winjavadriver.exe, auto-starts server
// (identical to how ChromeDriver works)
WinJavaDriver driver = new WinJavaDriver(options);
try {
// Standard Selenium API — returns WebElement, not a custom type
WebElement editArea = driver.findElement(WinBy.className("RichEditD2DPT"));
editArea.sendKeys("Hello from WinJavaDriver!");
// Selenium-standard screenshot
driver.getScreenshotAs(OutputType.FILE);
} finally {
// Close the session and stop the server
driver.quit();
}
}
}定位器策略
| 策略 | 描述 | 示例 |
|---|---|---|
WinBy.name(value) | 元素的Name属性 | WinBy.name("Save") |
WinBy.accessibilityId(value) | Automation Id(最可靠) | WinBy.accessibilityId("btnSave") |
WinBy.className(value) | Win32类名 | WinBy.className("Edit") |
By.tagName(value) | 控制类型 | By.tagName("button") |
By.xpath(expression) | XPath over UI树 | By.xpath("//Button[@Name='Save']") |
名称规范化
Windows控件通常包括加速器键前缀(例如。, "&Open" 用于Alt+O快捷键)。WinJavaDriver会自动规范名称,以便 WinBy.name("Open") 匹配两者 "Open" 和 "&Open"。无需配置。
发现元素定位器
GUI检查器(推荐)
启动独立的检查器工具:
winjavadriver-inspector.exe特征:
- 深色主题 (材质设计)采用Chrome DevTools风格布局
- 悬停高亮显示 --将鼠标悬停在元素上以查看其类型、名称和大小
- 单击以捕获 --单击任何元素(或Ctrl+Q)将其选中
- 多定位器面板 --使用复制按钮查看所有定位器策略(accessibilityId、name、className、xpath)
- Java代码面板 --准备粘贴Selenium/WinJavaDriver代码片段
- 定位器控制台 --根据实时UI树测试定位器
- VB6标签支持 --发现无窗口的VB6标签控件
- 面包屑导航 --单击路径段以浏览元素树
- DPI感知 --在高DPI显示器上正常工作
CLI检查模式(传统)
winjavadriver.exe --inspect录制与回放
Inspector包括一个内置的记录器,可以捕获用户交互并生成可执行的测试代码。
录制
- 打开检查器并单击 记录
- 检查器最小化并显示浮动记录工具栏(始终位于顶部,可拖动)
- 与应用程序正常交互——点击、打字和键盘快捷键都是被动捕获的
- 按 停止 在工具栏(或ESC)上完成录制
记录的内容:
- 点击 --单击、双击、右键单击元素标识
- 文本输入 --按键被缓冲并合并到SendKeys操作中(5秒刷新)
- 键盘快捷键 --Ctrl+S、Alt+F4、Ctrl+Shift+N等,带有修饰符跟踪功能
- 导航键 --箭头键、Tab键、Enter键、向上/向下翻页、主页、结束、退格、删除
- 截图 --每一步都会捕获一个元素截图以供视觉参考
附加功能:
- 暂停/恢复 --暂时暂停录制而不停止
- 可编辑文本框 --展开步骤以编辑记录的文本或预期值
- 添加评论 --用用户注释注释步骤
- 添加断言 --Ctrl+Shift+Click将元素名称捕获为断言
- 步骤管理 --重新排序、删除或修改记录的步骤
- 自我过滤 --在检查器或工具栏上的点击不会被记录
代码生成
录制后,单击 生成代码 生产:
- Java页面对象 --类与
By定位器字段、构造函数和performActions()方法 - JUnit5测试 --独立测试类
@BeforeEach/@AfterEach生命周期,@Test方法
生成的代码特征:
WebDriverWait.until()用于可靠的元素查找(默认超时10秒)- 通过基于标题的匹配进行窗口切换
getWindowHandles() Actions用于右键单击的类(contextClick)然后双击(doubleClick)Keys.chord()用于快捷键(Ctrl+S、Alt+F4等)- 导航键映射到Selenium
Keys常量 assertEquals()用于断言步骤- 当不存在稳定标识符时,基于位置的元素过滤(±20px容差)
- 用户注释保留为Java代码注释
回放
点击 回放 针对实时应用程序重新执行记录的步骤:
- 按Automation Id(首选)或Name+ClassName重新查找元素
- 通过标题匹配处理窗口切换
- 支持所有操作类型(单击、类型、快捷方式、断言)
- 按 退出 随时取消重播
API 参考
WinJavaDriver(扩展了Selenium的RemoteWebDriver)
import io.github.glaciousm.*;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.*;
// Auto-discover exe, auto-start server (like ChromeDriver)
WinJavaDriver driver = new WinJavaDriver(options);
// Or connect to an already-running server
WinJavaDriver driver = new WinJavaDriver(new URL("http://localhost:9515"), options);
// Standard Selenium API — returns WebElement
WebElement element = driver.findElement(WinBy.name("Save"));
List elements = driver.findElements(By.tagName("button"));
// Screenshots
File screenshot = driver.getScreenshotAs(OutputType.FILE); // app window
File desktopShot = driver.getDesktopScreenshot(OutputType.FILE); // entire screen
// Page source, timeouts, cleanup
String xml = driver.getPageSource();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
driver.quit();等待助手
| 方法 | 说明 |
|---|---|
waitForElement(By locator) | 等待元素存在于UI树中(默认10秒) |
waitForElement(By locator, Duration timeout) | 使用自定义超时等待 |
waitForClickable(By locator) | 等待元素可见并启用(默认10秒) |
waitForClickable(By locator, Duration timeout) | 等待可点击的自定义超时 |
waitForWindowCount(int count) | 等待N个窗口打开(默认10秒),返回句柄 |
waitForWindowCount(int count, Duration timeout) | 使用自定义超时等待窗口计数 |
WebElement el = driver.waitForElement(WinBy.name("Ready"));
WebElement btn = driver.waitForClickable(WinBy.name("Save"), Duration.ofSeconds(5));
Set handles = driver.waitForWindowCount(2);窗口管理
| 方法 | 说明 |
|---|---|
switchBack() | 切换到上次之前打开的窗口 switchTo().window() 呼叫 |
switchToWindowByTitle(String titleFragment) | 按标题查找并切换到系统上的任何窗口(区分大小写包含匹配项) |
listAllWindows() | 列出系统上所有可见的窗口--返回 List 随着 handle, title, className, processId |
// Switch to popup, interact, switch back — no need to store the main handle
driver.switchTo().window(popupHandle).findElement(WinBy.name("OK")).click();
driver.switchBack();
// Find and switch to a child window from a different process
driver.switchToWindowByTitle("POS_PREPROD");
// Discover all windows (useful when getWindowHandles() doesn't return child windows)
List> windows = driver.listAllWindows();
windows.forEach(w -> System.out.println(w.get("title") + " → " + w.get("handle")));元素查找助手
| 方法 | 说明 |
|---|---|
findElementByIndex(By locator, int index) | 找到与定位器匹配的第N个元素(从0开始)。适用于没有唯一名称的VB6控件。 |
findElementByPosition(By locator, int row, int col) | 通过视觉网格位置查找元素。按Y坐标(±20px)对元素进行分组,每行按X排序。 |
findElementByPosition(By locator, int row, int col, int tolerance) | 与自定义Y公差相同(默认为20px,高DPI时增加)。 |
findChildren(WebElement parent) | 获取一个元素的所有直接子元素。适用于UIA和Win32/VB6(MSAA)。 |
findChildren(WebElement parent, int index) | 获取第N个直接子项(从0开始)。 |
// VB6 form with 10 identical TextBox controls — get the 3rd one
WebElement field = driver.findElementByIndex(WinBy.className("ThunderRT6TextBox"), 2);
// Grid-like layout — find element at row 1, column 3
WebElement cell = driver.findElementByPosition(WinBy.className("ThunderRT6TextBox"), 1, 3);
// Enumerate children of a container
List children = driver.findChildren(panel);
WebElement firstChild = driver.findChildren(panel, 0);输入并重试
| 方法 | 说明 |
|---|---|
sendKeys(CharSequence... keys) | 将密钥发送到活动/聚焦元素,而不需要元素引用。 |
retry(Runnable action, int maxAttempts, Duration delay) | 最多重试N次操作,两次尝试之间有延迟。毕竟投掷失败了。 |
// Keyboard shortcuts without finding an element first
driver.sendKeys(Keys.chord(Keys.CONTROL, "o")); // Ctrl+O
driver.sendKeys(Keys.ENTER);
// Retry a flaky VB6 click
driver.retry(() -> driver.findElement(WinBy.name("Save")).click(), 3, Duration.ofMillis(500));WebElement(标准硒)
// Interactions
element.click();
element.clear();
element.sendKeys("text to type");
// Properties
String text = element.getText();
String tagName = element.getTagName();
boolean enabled = element.isEnabled();
boolean displayed = element.isDisplayed();
String attr = element.getAttribute("ClassName");
Rectangle rect = element.getRect();
// Find child elements
WebElement child = element.findElement(WinBy.name("Child"));
List children = element.findElements(By.tagName("listitem"));W3C操作API
硒 Actions 类完全支持复杂的交互:
import org.openqa.selenium.interactions.Actions;
Actions actions = new Actions(driver);
// Right-click (context menu)
actions.contextClick(element).perform();
// Double-click
actions.doubleClick(element).perform();
// Hover over element
actions.moveToElement(element).perform();
// Drag and drop
actions.dragAndDrop(source, target).perform();
// Keyboard shortcut (Ctrl+S)
actions.keyDown(Keys.CONTROL).sendKeys("s").keyUp(Keys.CONTROL).perform();
// Ctrl+Click
actions.keyDown(Keys.CONTROL).click(element).keyUp(Keys.CONTROL).perform();
// Key combos with modifier tracking (proper release order)
actions.keyDown(Keys.SHIFT).sendKeys(Keys.F10).keyUp(Keys.SHIFT).perform();显式等待(硒标准)
import org.openqa.selenium.support.ui.*;
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// Wait for element to be present in the UI tree
WebElement element = wait.until(
ExpectedConditions.presenceOfElementLocated(WinBy.name("Ready")));
// Wait for element to be visible (present + isDisplayed)
WebElement visible = wait.until(
ExpectedConditions.visibilityOfElementLocated(WinBy.name("Ready")));
// Wait for element to be clickable (visible + isEnabled)
WebElement clickable = wait.until(
ExpectedConditions.elementToBeClickable(WinBy.name("Save")));
// Custom condition with lambda
wait.until(d -> {
WebElement el = d.findElement(WinBy.name("Status"));
return el.getText().contains("Done") ? el : null;
});WinJava选项
WinJavaOptions options = new WinJavaOptions()
.setApp("C:\\Program Files\\MyApp\\app.exe") // App to launch
.setAppArguments("--flag value") // Command line args
.setAppWorkingDir("C:\\Working") // Working directory
.setWaitForAppLaunch(10) // Seconds to wait
.setShouldCloseApp(true); // Close on quit
// Or attach to running app by window handle
WinJavaOptions options = new WinJavaOptions()
.setAppTopLevelWindow("0x1A2B3C"); // Hex window handle旧版Win32/VB6应用程序支持
WinJavaDriver会自动检测UI自动化不可见的遗留应用程序,并退回到其他发现方法。不需要配置,它只是工作。
VB6特殊处理
- **雷霆\*控制** (ThunderRT6TextBox、ThunderRT6Combox等):标准UIA输入法在这些控件上会自动失败。WinJavaDriver检测Thunder\*类名并使用Win32消息。
- VB6标签:尽管没有窗口句柄,但仍可发现。通过以下方式找到
WinBy.className("VB6Label").
// VB6 apps work the same as modern apps
WinJavaDriver driver = new WinJavaDriver(
new WinJavaOptions().setApp("C:\\path\\to\\LegacyApp.exe"));
// VB6 Labels are discoverable
List labels = driver.findElements(WinBy.className("VB6Label"));
labels.forEach(l -> System.out.println(l.getText())); // runtime captions自定义服务器端点
WinJavaDriver使用自定义端点扩展了W3C WebDriver协议:
| 方法 | 终点 | 目的 |
|---|---|---|
| 得到 | /session/{id}/winjavadriver/element/{eid}/children | 获取直接子项(UIA+Win32/VB6) |
| 得到 | /session/{id}/winjavadriver/windows/all | 列出系统上所有可见的窗口 |
| 得到 | /session/{id}/winjavadriver/screenshot/desktop | 完整桌面截图 |
| 职位 | /session/{id}/winjavadriver/grid/{eid}/cell | 创建虚拟MSFlexGrid单元元素 |
| 得到 | /session/{id}/winjavadriver/grid/{eid}/info | 获取网格尺寸并编辑字段信息 |
| 职位 | /session/{id}/winjavadriver/grid/{eid}/cell/value | 读取MSFlexGrid单元格值 |
| PUT | /session/{id}/winjavadriver/grid/{eid}/cell/value | 写入MSFlexGrid单元格值 |
- 行/列从0开始(不包括标题行)
- MCP服务器通过以下方式公开此信息
win_grid_edit用于批量单元格编辑
MCP服务器(AI桌面自动化)
这 mcp/ 目录包含一个MCP服务器,该服务器使AI代理能够使用令牌高效的智能工具自动化Windows桌面应用程序。
设置
{
"mcpServers": {
"winjavadriver": {
"command": "node",
"args": ["
/mcp/dist/index.js"],
"env": {
"WINJAVADRIVER_PORT": "9515"
}
}
}
}构建MCP服务器:
cd mcp
npm install
npm run build智能工具(AI优化)
这些工具将多个WebDriver调用组合成单个、令牌高效的操作:
| 工具 | 说明 |
|---|---|
win_observe | 一次调用中的截图+元素摘要——主要的“查看屏幕”工具 |
win_explore | 简洁的元素摘要,位置为@(x,y),没有id警告 |
win_interact | 在一次调用中查找+操作(单击、键入、清除、clear_and_type、right_click、double_click和read) |
win_batch | 按顺序执行多个查找和操作步骤(在一次调用中填写表单) |
win_read_all | 从多个元素批量读取文本/属性 |
win_wait_for | 服务器端轮询(element_visible、element_gone、text_equals等)——等待期间令牌成本为零 |
win_diff | 显示自上次探索以来发生了什么变化(新的、删除的、更改的元素) |
win_hover | 使用W3C Actions API将鼠标悬停在元素上 |
win_form_fields | 发现具有当前值的表单字段(编辑、组合框、复选框) |
win_menu | 通过按顺序单击项目来导航菜单路径(例如,“文件”>“另存为”) |
win_select_option | 从组合框/列表框中选择选项--展开、查找、单击 |
win_grid_edit | 在一次调用中批量编辑多个MSFlexGrid单元格 |
首选AI代理工作流程:
win_observe--查看屏幕(屏幕截图+元素摘要)win_interact或win_batch--执行操作win_diff或win_observe--验证结果win_wait_for--当时间很重要时(对话框、加载)
标准工具
| 工具 | 说明 |
|---|---|
win_launch_app | 使用可选功能启动应用程序 verbose: true 用于调试 |
win_attach_app | 通过窗口句柄连接到正在运行的应用程序 |
win_quit | 关闭会话和应用程序 |
win_find_element | 查找单个元素(名称、可访问性id、类名、标记名、xpath) |
win_find_elements | 使用可选元素查找多个元素 includeInfo: true |
win_click | 点击元素(支持x/y偏移) |
win_type | 在元素中键入文本 |
win_clear | 清除元素值 |
win_send_keys | 发送具有重复语法的键盘按键(DOWN*5) |
win_get_text | 获取元素文本 |
win_get_attribute | 获取元素属性 |
win_element_info | 获取元素信息(文本、矩形、类名、automationId、名称、启用、显示) |
win_screenshot | 窗口、元素或整个屏幕的截图(fullscreen: true) |
win_page_source | 以XML格式获取UI树 |
win_window_handle | 获取当前窗口句柄 |
win_list_windows | 列出当前进程的窗口句柄 |
win_list_all_windows | 列出所有可见窗口(标题、句柄、PID) |
win_switch_window | 切换到其他窗口 |
win_set_window | 最大化、最小化或全屏 |
win_close_window | 关闭当前窗口 |
win_clipboard | 读/写系统剪贴板 |
win_get_logs | 获取服务器详细日志 |
win_set_verbose | 启用/禁用详细日志记录 |
win_clear_logs | 清除日志缓冲区 |
win_status | 检查服务器是否正在运行 |
服务器CLI
winjavadriver.exe [options]
Options:
--port
Port to listen on (default: 9515)
--host Host to bind to (default: localhost)
--verbose Enable verbose logging
--log-file
Write logs to file
--inspect Launch inspect mode (element spy)
--version Print version
--help Show help通过Selenium Grid进行远程执行
使用Selenium Grid 4在远程Windows计算机上运行桌面UI测试。WinJavaDriver通过内置的中继功能进行集成,这与Appium使用的模式相同。
// Point tests at the Grid — routes to WinJavaDriver node automatically
WinJavaDriver driver = new WinJavaDriver(
new URL("http://grid-machine:4444"), options);有关完整的设置说明,请参阅 .
UWP应用程序(计算器、绘图等)
完全支持UWP应用程序:
// Launch Windows Calculator (UWP app)
WinJavaOptions options = new WinJavaOptions()
.setApp("calc.exe")
.setWaitForAppLaunch(10);
WinJavaDriver driver = new WinJavaDriver(options);
// Find and click button "Five"
driver.findElement(WinBy.name("Five")).click();
driver.findElement(WinBy.name("Plus")).click();
driver.findElement(WinBy.name("Three")).click();
driver.findElement(WinBy.name("Equals")).click();
// Get result
WebElement result = driver.findElement(WinBy.accessibilityId("CalculatorResults"));
System.out.println(result.getText()); // "Display is 8"注: 对于UWP应用程序。, calc.exe)立即退出,实际应用程序作为不同的进程运行。WinJavaDriver会自动处理此问题。
从源头构建
客户端(Java)
cd client-java
mvn clean installMCP服务器(Node.js)
cd mcp
npm install
npm run build建筑
┌─────────────────────────────────────────────────────────┐
│ Java Client │
│ WinJavaDriver (extends RemoteWebDriver) │
│ WinBy → WebElement → WebDriverWait │
│ WinJavaDriverService (extends DriverService) │
└─────────────────────┬───────────────────────────────────┘
│ W3C WebDriver Protocol
│ (HTTP + JSON)
┌─────────────────────▼───────────────────────────────────┐
│ winjavadriver.exe (server) │
│ Handles element discovery, interaction, screenshots │
│ Supports UIA, Win32, MSAA, and VB6 controls │
└─────────────────────────────────────────────────────────┘client-java/ (Java client extending Selenium RemoteWebDriver)
mcp/ (MCP server for AI-driven automation)
examples/ (Cucumber BDD test examples)
configs/ (Selenium Grid Node TOML config templates)
scripts/ (Node setup scripts)
jenkins/ (Docker-based Jenkins CI/CD)
docs/ (Documentation)支持的控件类型
按钮、日历、复选框、组合框、自定义、DataGrid、数据项、文档、编辑、组、标题、HeaderItem、超链接、图像、列表、列表项、菜单、菜单栏、菜单项、窗格、进度条、单选按钮、滚动条、分隔符、滑块、旋转器、SplitButton、状态栏、选项卡、选项卡项、表、文本、拇指、标题栏、工具栏、工具提示、树、树项、窗口
示例项目
这 examples/ 目录包含完整的Cucumber BDD测试项目:
| 项目 | 描述 |
|---|---|
calculator-tests | Windows 11+VB6计算器自动化(3种情况) |
运行示例
cd examples/calculator-tests
mvn test该示例使用SeleniumHQ模式——没有硬编码路径,没有手动服务器管理:
// Each driver auto-discovers winjavadriver.exe and manages its own server
WinJavaDriver driver = new WinJavaDriver(
new WinJavaOptions().setApp("calc.exe").setWaitForAppLaunch(10));
// ...
driver.quit(); // auto-stops the server故障排除
未找到元素
- 使用检查器GUI验证元素是否存在并查看其属性
- 检查元素是否在其他窗口中--使用
driver.switchTo().window(handle) - 为动态元素添加显式等待
- 尝试不同的定位器策略(accessibilityId最可靠)
会话创建失败
- 确保应用程序路径正确
- 检查应用程序是否需要提升权限
- 验证应用程序窗口是否在超时时间内出现
单击不起作用
- 确保元素可见且已启用
- 尝试使用
sendKeys("\n")按钮 - 对于复杂的交互,请使用
Actions类(右键单击、双击、悬停)
VB6 sendKeys不工作
VB6 Thunder\*控件忽略标准UIA输入法。WinJavaDriver会自动检测到这一点,并使用Win32消息。请注意,这将替换整个文本--call element.clear() 在链接多个之前 sendKeys() 电话。
详细日志
启用详细日志记录以调试问题:
WinJavaDriverService service = new WinJavaDriverService.Builder()
.withVerboseLogging(true)
.build();贡献
欢迎投稿!请打开问题或拉取请求。
许可证
MIT许可证。看 许可证 文件。
