Skip to content

Commit 5822b40

Browse files
authored
Merge pull request #355 from Integration-Automation/dev
Release: vision, locate, window-management & test-robustness lanes (v115–v144)
2 parents b152393 + 1037919 commit 5822b40

156 files changed

Lines changed: 11051 additions & 55 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README/WHATS_NEW_zh-CN.md

Lines changed: 180 additions & 0 deletions
Large diffs are not rendered by default.

README/WHATS_NEW_zh-TW.md

Lines changed: 180 additions & 0 deletions
Large diffs are not rendered by default.

WHATS_NEW.md

Lines changed: 180 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
Check-Digit Algorithms
2+
======================
3+
4+
``pii_text`` detects credit-card and IBAN *shapes* by regex and ``data_quality``
5+
does type / range / regex validation, but nothing actually computes or verifies a
6+
*check digit*. This adds the shared arithmetic engine for the four schemes behind
7+
most real-world identifiers — and the primitive that account-number, card, IBAN,
8+
ISBN and EAN validation build on.
9+
10+
Pure standard library (integer arithmetic; the Verhoeff and Damm tables are small
11+
embedded constants). Every function is pure (string in, bool / str out), so it is
12+
fully deterministic in CI.
13+
14+
Headless API
15+
------------
16+
17+
.. code-block:: python
18+
19+
from je_auto_control import (
20+
luhn_validate, luhn_check_digit,
21+
verhoeff_validate, verhoeff_check_digit,
22+
damm_validate, damm_check_digit,
23+
mod97_10_validate, mod97_10_check_digits,
24+
)
25+
26+
luhn_validate("4111111111111111") # True (credit-card / IMEI)
27+
luhn_check_digit("7992739871") # '3' -> 79927398713
28+
verhoeff_validate("2363") # True (catches transpositions)
29+
damm_check_digit("572") # '4'
30+
mod97_10_validate("3214282912345698765432161182") # True (IBAN engine)
31+
32+
- **Luhn** (mod 10): credit cards, IMEI, many national IDs — catches all
33+
single-digit errors and most adjacent transpositions.
34+
- **Verhoeff** and **Damm**: decimal schemes that catch *all* single-digit and
35+
adjacent-transposition errors (stronger than Luhn).
36+
- **ISO 7064 MOD 97-10**: the two-check-digit scheme behind IBAN and similar.
37+
38+
Each scheme exposes ``*_validate(number)`` (does the value incl. its check digit
39+
verify?) and ``*_check_digit`` / ``*_check_digits`` (what digit(s) to append to a
40+
bare payload?). Non-digit characters are ignored, so spaced/grouped input works.
41+
42+
Executor commands
43+
-----------------
44+
45+
``AC_checksum_validate`` takes a ``scheme`` (``luhn`` / ``verhoeff`` / ``damm`` /
46+
``mod97``) plus a ``number`` and returns ``{valid}``; ``AC_checksum_digit`` returns
47+
``{check_digit}`` for a ``partial``. Both are exposed as MCP tools
48+
(``ac_checksum_validate`` / ``ac_checksum_digit``) and as Script Builder commands
49+
under **Data**.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Multi-Waypoint Mouse Gestures
2+
=============================
3+
4+
``humanize.humanized_path`` and ``tween_drag`` only interpolate a *single*
5+
start → end hop. Real gestures — signatures, marquee / rubber-band selections,
6+
dragging through several drop targets, shape gestures — need an arbitrary chain
7+
of waypoints, with the button optionally held down across the whole path.
8+
9+
:func:`plan_path` is pure point math (reusing the named easings from
10+
``tween_drag``) and is unit-testable on its own; :func:`move_along_path` and
11+
:func:`drag_path` dispatch through an injectable ``sink`` so the gesture is
12+
tested without real input. Imports no ``PySide6``.
13+
14+
Headless API
15+
------------
16+
17+
.. code-block:: python
18+
19+
from je_auto_control import (
20+
plan_path, move_along_path, drag_path, path_easings,
21+
)
22+
23+
# the eased point list through every waypoint (junctions de-duplicated)
24+
plan_path([(100, 100), (400, 150), (400, 500)], per_segment_steps=20)
25+
26+
move_along_path([(100, 100), (400, 150), (400, 500)]) # hover a polyline
27+
drag_path([(50, 50), (300, 50), (300, 300)], button="mouse_left") # L-drag
28+
29+
``plan_path`` interpolates each consecutive pair with ``per_segment_steps`` eased
30+
steps (``easing`` is any name from ``path_easings()`` — ``linear`` /
31+
``ease_in_out_quad`` / ``ease_out_cubic`` / ``ease_in_cubic``) and does not
32+
duplicate the shared junction points. ``move_along_path`` emits move events
33+
through the path; ``drag_path`` presses at the first waypoint, moves through the
34+
whole path, and releases at the last — for multi-stop drags. Both take a ``sink``
35+
override for headless testing.
36+
37+
Executor commands
38+
-----------------
39+
40+
``AC_move_along_path`` and ``AC_drag_path`` take ``waypoints`` (a JSON
41+
``[[x, y], ...]`` list) plus ``easing`` / ``per_segment_steps`` (and ``button``
42+
for the drag). Both are exposed as MCP tools (``ac_move_along_path`` /
43+
``ac_drag_path``) and as Script Builder commands under **Mouse**.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Clear-Then-Type Field Entry
2+
===========================
3+
4+
Setting a field's value reliably means *clearing* whatever is there first, then
5+
entering the new text — otherwise automation appends to or corrupts the existing
6+
content. The framework has ``write`` (types, but raises on emoji / CJK / chars
7+
outside the layout table) and ``set_clipboard`` / ``hotkey`` separately, but no
8+
single "focus → clear → set value" primitive and no paste strategy for text that
9+
``write`` cannot type. This adds the Playwright ``fill`` idiom.
10+
11+
:func:`plan_field_set` builds the deterministic op-plan (pure, unit-testable);
12+
:func:`set_field_text` dispatches it through an injectable ``sink`` so it is
13+
tested without real input. Imports no ``PySide6``.
14+
15+
Headless API
16+
------------
17+
18+
.. code-block:: python
19+
20+
from je_auto_control import set_field_text, plan_field_set
21+
22+
set_field_text("new value") # select-all, delete, type
23+
set_field_text("café 🚀", paste=True) # via clipboard (Unicode-safe)
24+
set_field_text("appended", clear="none") # no clear, just type
25+
set_field_text("", paste=True, modifier="command") # macOS
26+
27+
plan_field_set("hi")
28+
# [{'op': 'hotkey', 'keys': ['ctrl', 'a']},
29+
# {'op': 'key', 'key': 'delete'},
30+
# {'op': 'type', 'text': 'hi'}]
31+
32+
``clear`` is ``"select_all"`` (the ``modifier``+A then Delete clear) or
33+
``"none"``. ``paste=True`` enters the text through the clipboard (``modifier``+V)
34+
— the reliable path for Unicode / emoji / CJK that ``write`` cannot type — rather
35+
than typing key by key. ``modifier`` is the platform command key (``"ctrl"``; use
36+
``"command"`` on macOS). An unknown ``clear`` mode raises ``ValueError``.
37+
38+
Executor commands
39+
-----------------
40+
41+
``AC_set_field_text`` takes ``text`` plus ``clear`` / ``paste`` / ``modifier`` and
42+
returns ``{ops, plan}``. It is exposed as the MCP tool ``ac_set_field_text`` and
43+
as a Script Builder command under **Keyboard**.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
Wait Until Gone (Blocking Vanish Waits)
2+
=======================================
3+
4+
``wait_for_image`` / ``wait_for_text`` block until something *appears*, and the
5+
``observer`` fires async callbacks on vanish — but there was no *blocking* "wait
6+
until this spinner / toast / dialog **disappears** then continue" call for an
7+
image or text. ``wait_until_window_closed`` covers windows only. This adds the
8+
missing vanish waits to the ``smart_waits`` family.
9+
10+
The generic :func:`wait_until_gone` takes any predicate, so its loop is
11+
headless-testable without a real screen; the image / text helpers build that
12+
predicate from the locate functions. Imports no ``PySide6``.
13+
14+
Headless API
15+
------------
16+
17+
.. code-block:: python
18+
19+
from je_auto_control import (
20+
wait_until_gone, wait_until_image_gone, wait_until_text_gone,
21+
)
22+
23+
# generic: wait until any predicate has been falsey
24+
wait_until_gone(lambda: spinner_is_visible(), timeout_s=15)
25+
26+
wait_until_image_gone("spinner.png", timeout_s=15) # image left the screen
27+
wait_until_text_gone("Loading...", timeout_s=15) # OCR text disappeared
28+
29+
Each returns a ``WaitOutcome`` (``succeeded`` / ``reason`` / ``elapsed_s`` /
30+
``samples_taken``) — the same result type as the other smart waits. ``gone_for_s``
31+
requires the target to stay absent for that long before succeeding (debounces a
32+
flickering element); ``poll_interval_s`` / ``timeout_s`` bound the loop.
33+
34+
Executor commands
35+
-----------------
36+
37+
``AC_wait_image_gone`` and ``AC_wait_text_gone`` take the target plus
38+
``timeout_s`` / ``poll_interval_s`` / ``gone_for_s`` (and ``detect_threshold`` for
39+
the image) and return the ``WaitOutcome`` dict. Both are exposed as MCP tools
40+
(``ac_wait_image_gone`` / ``ac_wait_text_gone``) and as Script Builder commands
41+
under **Flow**.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Hold Key / Auto-Repeat
2+
======================
3+
4+
``type_keyboard`` is an instant down+up and ``input_macro.run_sequence`` can
5+
hand-roll a press / wait / release, but there was no primitive for "hold this key
6+
for N seconds" (game movement, hold-to-scroll) or "send it at R presses per
7+
second" (auto-repeat).
8+
9+
:func:`plan_key_hold` builds the deterministic op-plan (pure, unit-testable);
10+
:func:`hold_key` dispatches it through an injectable ``sink`` and ``sleep`` so it
11+
is tested without real input or real waiting. Imports no ``PySide6``.
12+
13+
Headless API
14+
------------
15+
16+
.. code-block:: python
17+
18+
from je_auto_control import hold_key, plan_key_hold
19+
20+
hold_key("key_d", duration_s=1.5) # press, hold 1.5s, release
21+
hold_key("key_down", duration_s=2.0, rate_hz=20) # 40 key events @ 50ms
22+
23+
plan_key_hold("space", 1.0)
24+
# [{'op': 'press', 'key': 'space'},
25+
# {'op': 'wait', 'seconds': 1.0},
26+
# {'op': 'release', 'key': 'space'}]
27+
28+
With ``rate_hz`` unset the key is pressed, held for ``duration_s``, then released.
29+
With ``rate_hz`` set it is sent as ``round(duration_s * rate_hz)`` discrete key
30+
events spaced ``1 / rate_hz`` apart — simulated auto-repeat for movement / scroll
31+
loops. A non-positive duration or rate raises ``ValueError``. ``hold_key`` routes
32+
the ``wait`` steps to ``sleep`` and the key steps to ``sink``, both injectable.
33+
34+
Executor commands
35+
-----------------
36+
37+
``AC_hold_key`` takes ``key`` plus ``duration_s`` and an optional ``rate_hz`` and
38+
returns ``{ops, plan}``. It is exposed as the MCP tool ``ac_hold_key`` and as a
39+
Script Builder command under **Keyboard**.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Relative Mouse Movement
2+
=======================
3+
4+
The mouse wrapper exposes only absolute ``set_mouse_position`` — there was no
5+
"nudge the pointer by ``(dx, dy)``" (the pynput / PyAutoGUI ``moveRel`` staple),
6+
which relative-pointer / canvas / FPS-style apps and incremental drags need.
7+
8+
:func:`relative_target` is the pure arithmetic (current + delta) and is
9+
unit-testable; :func:`move_mouse_relative` reads the live position and sets the
10+
new one, with both the getter and setter injectable so it is tested without a
11+
real pointer. Imports no ``PySide6``.
12+
13+
Headless API
14+
------------
15+
16+
.. code-block:: python
17+
18+
from je_auto_control import move_mouse_relative, relative_target
19+
20+
move_mouse_relative(-40, 12) # nudge left 40, down 12 from where it is
21+
# {'from': [200, 200], 'to': [160, 212], 'delta': [-40, 12]}
22+
23+
relative_target((100, 100), 10, -5) # (110, 95) — pure, no I/O
24+
25+
``move_mouse_relative`` reads the current position (raising
26+
``AutoControlMouseException`` if it cannot), adds the delta, and moves there.
27+
``get_position`` / ``set_position`` default to the real mouse wrapper but are
28+
injectable for headless tests.
29+
30+
Executor commands
31+
-----------------
32+
33+
``AC_move_mouse_relative`` takes ``dx`` / ``dy`` and returns ``{from, to,
34+
delta}``. It is exposed as the MCP tool ``ac_move_mouse_relative`` and as a
35+
Script Builder command under **Mouse**.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Wait for Region Colour
2+
======================
3+
4+
``wait_for_pixel`` matches a single point exactly and ``wait_until_pixel_changes``
5+
detects *any* change at one point — neither answers "wait until this status light
6+
turns green", "until the progress bar is mostly filled", or "until the red error
7+
banner is gone". This adds a region-colour wait to the ``smart_waits`` family.
8+
9+
The pixel counting is a pure helper and :func:`wait_until_color` takes an
10+
injectable ``sampler``, so the loop is headless-testable without a real screen.
11+
Imports no ``PySide6``.
12+
13+
Headless API
14+
------------
15+
16+
.. code-block:: python
17+
18+
from je_auto_control import wait_until_color
19+
20+
# wait until ≥ 60% of the region is (near) green
21+
wait_until_color(region=[10, 10, 210, 40], target_rgb=[0, 200, 0],
22+
tolerance=15, min_fraction=0.6, timeout_s=20)
23+
24+
# wait until a red banner disappears
25+
wait_until_color(region=[0, 0, 800, 60], target_rgb=[200, 0, 0],
26+
present=False, timeout_s=10)
27+
28+
Pixels within ``tolerance`` (per channel) of ``target_rgb`` are counted. With
29+
``present=True`` the wait succeeds once that fraction reaches ``min_fraction``;
30+
with ``present=False`` once it drops below it. The result is a ``WaitOutcome``
31+
(``succeeded`` / ``reason`` / ``elapsed_s`` / ``samples_taken``).
32+
33+
Executor commands
34+
-----------------
35+
36+
``AC_wait_color`` takes ``target_rgb`` (and optional ``region``) as JSON arrays
37+
plus ``tolerance`` / ``min_fraction`` / ``present`` / ``timeout_s`` /
38+
``poll_interval_s``, and returns the ``WaitOutcome`` dict. It is exposed as the
39+
MCP tool ``ac_wait_color`` and as a Script Builder command under **Flow**.

0 commit comments

Comments
 (0)