Skip to main content

pyjab

Python implementation for Java application UI automation with Java Access Bridge.

pyjab is a Selenium-like library for driving Java desktop applications (Swing / AWT / JavaFX) on Windows. It talks to the Java Access Bridge API to read the accessibility tree of a running Java application, so you can find elements, read their text, fill forms, click buttons and read tables – without the target application exposing any API of its own.

  • Platform: Windows only.

  • Requires: a JDK (or a standalone Java Access Bridge) and Java Access Bridge enabled in the target application.

  • Locators: Selenium-style find_element_by_* plus an XPath-like syntax.

  • License: GPLv2 – see License and commercial use below.

How it works

pyjab loads WindowsAccessBridge-<bitness>.dll into the Python process and calls the Java Access Bridge C API through ctypes. Every JABElement you get back is a thin wrapper around one node of the target application’s accessibility tree.

This means:

  • it never needs to touch, modify or restart the target application;

  • it is not screen scraping or image matching – it reads real accessibility metadata (roles, states, text, tables, selections);

  • it only sees what the application exposes through Java Access Bridge.

Installation

$ pip install pyjab

Prerequisites

  1. Windows. pyjab imports pywin32 and loads a Windows DLL.

  2. A JDK, JRE or standalone Java Access Bridge on the machine running the automation. The DLL ships with the JDK.

  3. Java Access Bridge enabled for the user running the automation. pyjab does this for you: on first use it writes %USERPROFILE%\.accessibility.properties if the file is missing or does not already enable the bridge.

Where the DLL lives depends on your JDK version:

JDK version

DLL location

JDK 8, 9, 10

%JAVA_HOME%\jre\bin\WindowsAccessBridge-64.dll

JDK 11 and newer

%JAVA_HOME%\bin\WindowsAccessBridge-64.dll (the bundled jre directory no longer exists)

Standalone JAB

%JAB_HOME%\WindowsAccessBridge-64.dll

You normally do not need to configure anything. pyjab searches %JAVA_HOME%\bin, %JAVA_HOME%\jre\bin, %JDK_HOME%, %JRE_HOME%, %JAB_HOME%, the common vendor install locations (Adoptium, Corretto, Zulu, Microsoft, IntelliJ-downloaded JDKs, scoop) and finally does a bounded recursive search under your JDK directories.

If discovery fails, the error message lists everything it probed and how to fix it. You can also point pyjab at a specific file:

JABDriver(title="My Application",
          bridge_dll=r"C:\Program Files\Java\jdk-21\bin\WindowsAccessBridge-64.dll")

Quickstart

Step 1 – start the Java application you want to automate, then find its window title. The title must match exactly (it is matched with fnmatch, so wildcards are allowed).

Step 2 – write your first script:

from pyjab.common.by import By
from pyjab.jabdriver import JABDriver

# Bind to an already-running Java application by window title.
driver = JABDriver(title="My Application")

# Find a control by its accessible name and click it.
driver.find_element_by_name("Login").click()

# Wait for something to appear.
dashboard = driver.wait_until_element_exist(By.NAME, "Dashboard", timeout=30)

# Read a value.
print(dashboard.name, dashboard.role, dashboard.states)

Using the context manager closes the driver and terminates the bound Java process when the block exits:

with JABDriver(title="My Application") as driver:
    driver.find_element_by_name("Login").click()

To let pyjab launch the application for you, pass file_path:

# A .jnlp is launched through `javaws`; anything else is executed directly.
with JABDriver(file_path=r"C:\jnlps\test.jnlp",
               title="My Application") as driver:
    driver.find_element_by_name("Login").click()

Finding locators

This is the part people get stuck on, so it is worth reading carefully.

You write locators for the accessible name, role, description or state of a control – the same information a screen reader would announce. To see it, install Access Bridge Explorer (Windows) and expand the accessibility tree of your application. Each node shows exactly the fields pyjab exposes: name, description, role, states, indexInParent, bounds and so on.

Common patterns:

# By accessible name (the label a screen reader would read).
driver.find_element_by_name("Submit")

# By role, when the control has no useful name.
driver.find_element_by_role("push button")

# By role and state, when several controls share a role.
driver.find_element(by=By.STATES, value="enabled,focusable,visible,showing")

# By index among siblings, when the control has neither name nor useful role.
driver.find_element_by_index_in_parent(3)

# XPath-like traversal, for complex hierarchies.
driver.find_element_by_xpath("//internal frame[@name='FRM-999']")
driver.find_element_by_xpath("//push button[@name=contains('OK')]")

# All matches, not just the first one.
buttons = driver.find_elements_by_role("push button")

Available By strategies: NAME, DESCRIPTION, ROLE, STATES, OBJECT_DEPTH, CHILDREN_COUNT, INDEX_IN_PARENT, XPATH.

Working with elements

element = driver.find_element_by_name("Username")

# Read properties.
element.name          # accessible name
element.role          # e.g. "push button", "text", "table"
element.states        # e.g. "enabled,focusable,visible,showing"
element.bounds        # {'x': .., 'y': .., 'width': .., 'height': ..}
element.text          # text content, for accessible-text elements
element.table         # row/column info, for table elements
element.is_enabled    # also: is_visible, is_showing, is_checked,
                      #       is_selected, is_editable

# Interact.
element.click()
element.send_text("hello")
element.clear()
element.select("Option A")     # combo boxes, lists, tabs
element.scroll(to_bottom=True)
element.expand()

# Screenshots.
element.get_screenshot_as_file("./element.png")
driver.get_screenshot_as_file("./window.png")

The simulate parameter

Most interaction methods accept simulate=. It is worth understanding, because the two modes have genuinely different trade-offs:

simulate=False (default)

pyjab drives the control through the Java Access Bridge accessibility action API. This is more reliable for controls whose bounds are unknown or invalid (see the -1 bounds case in the troubleshooting section) and it does not need the window to be in the foreground. It is the safer default.

simulate=True

pyjab moves the real mouse cursor to the control’s centre and clicks. Use this when the accessibility action does nothing – for example some custom or third-party components. It brings the target window to the foreground, which is disruptive if you need the machine for anything else, and it fails in non-interactive sessions such as a CI agent running as a service.

Rule of thumb: start with the default, and only reach for simulate=True when the accessibility action is ignored.

Limitations

pyjab can only see what Java Access Bridge exposes. The following are not supported, and no amount of client-side work will change that:

  • Canvas-drawn UIs. If a component paints its widgets itself onto a Canvas, there is nothing in the accessibility tree to find. Access Bridge Explorer will show the canvas with no children.

  • Java applets or Java embedded in a browser / Electron shell. These typically run in a separate process that does not expose the accessibility bridge to the desktop. Access Bridge Explorer cannot see them either, which is the quickest way to confirm it.

  • Anything not exposed as accessible. Some custom components simply do not implement the accessibility interfaces.

  • Off-screen table rows. Reading cells that are scrolled out of view is unreliable and can destabilise the target application; scroll the table into view first.

If Access Bridge Explorer cannot see it, pyjab cannot see it. Always check there first.

Troubleshooting

FileNotFoundError: Java Access Bridge DLL ... could not be located

The DLL was not found. The message lists every directory that was probed and every environment variable it read. The three usual fixes:

  1. set JAVA_HOME to your JDK installation directory;

  2. set JAB_HOME to the directory containing the DLL;

  3. pass bridge_dll=r"...\WindowsAccessBridge-64.dll" explicitly.

Also check for a bitness mismatch: a 64-bit Python cannot load the 32-bit DLL. The message calls this out explicitly when it happens.

ModuleNotFoundError: No module named 'win32process'

Not on Windows. pyjab is Windows only.

pip install pyjab resolves dependencies very slowly or reports conflicts

You are on an old pyjab. Versions up to 1.1.7 declared both pypiwin32 and pywin32, which conflict. Upgrade to 1.2.0 or later.

JABException: JABElement with locator 'name' 'X' does not found

The locator did not match. Common causes: the window title bound to the wrong window; the dialog is modal and needs the pump to run (pyjab handles this for most cases); or the control’s accessible name differs from its visible label. Use Access Bridge Explorer to read the real name.

Elements report bounds = {'x': -1, 'y': -1, 'width': -1, 'height': -1}

The application does not report geometry for this control – common for table cells. simulate=True cannot work here, because there is no coordinate to click. Use the accessibility action API (the default simulate=False), or the table-specific helpers.

The target application becomes slow or unresponsive after a long run

Known issue with long-running sessions. Workarounds: reuse a single JABDriver instead of creating one per test, and call release_jabelement() on elements you are done with.

Development

$ pip install -e ".[dev]"
$ pytest                       # portable suite, runs on any OS

The GUI tests need Windows, a real JDK, real Swing applications and an interactive desktop session. They are opt-in:

$ set PYJAB_RUN_GUI_TESTS=1    # Windows
$ pytest

tests/conftest.py documents which fixtures exist and what they need.

Support

  • Bug reports and feature requests: please open an issue on GitHub. Include your JDK version, Python version and the window title you bound to.

  • Commercial support, integration help or custom development: contact gaozhao89@qq.com.

License and commercial use

pyjab is licensed under GPLv2.

This is worth understanding before you depend on it: GPLv2 is a copyleft licence. If you distribute software that links pyjab, that software must also be distributed under GPLv2. Using pyjab for internal automation that you never distribute does not trigger this, but shipping a product that bundles pyjab does.

If that is a problem for your use case, please get in touch – see Support. Relicensing is a topic the maintainer is open to discussing with contributors and users.

Contributing

See CONTRIBUTING.rst. Bug reports with a minimal reproduction are the most valuable contribution.

© 2021-2026 Gary Gao.

Metadata

Release files for pyjab 1.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyjab 1.2.0
File Size Uploaded
pyjab-1.2.0.tar.gz 112.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyjab 1.2.0
File Interpreter ABI Platform
pyjab-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 160.8 kB

Release files / pyjab-1.2.0.tar.gz

Download URL pyjab-1.2.0.tar.gz
Size 112.0 kB
Tags Source
SHA-256 checksum
How to use checksums
f856ab3c895d79398b71635641b1c9e8b2bbde0ac21ada1ba188b5a64311080c
BLAKE2b-256 checksum
How to use checksums
b19ecd3b96d519e4df200e1a489135a2719b63dea4b29a7d653255915855ce69
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / pyjab-1.2.0-py3-none-any.whl

Download URL pyjab-1.2.0-py3-none-any.whl
Size 48.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4ecf5cbcb1a92dd8d4fac1cf54ee5ba0722b21b22060da33fa45c7590abdc695
BLAKE2b-256 checksum
How to use checksums
dd8579d91dfe58bab8bee55a33001e16bd3357b34d2d41958a51706465b32d73
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

1.10.0

2 release files

1.9.0

2 release files

1.8.0

2 release files

1.7.0

2 release files

1.6.3

2 release files

1.6.2

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.3

2 release files

1.4.2

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.1

2 release files

This release

1.2.0 This release

2 release files

1.1.7

2 release files

1.1.6

2 release files

1.1.5

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page