PyClickerRPA 0.1.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyclickerrpa-0.3.0/LICENSE +21 -0
- pyclickerrpa-0.3.0/PKG-INFO +138 -0
- pyclickerrpa-0.3.0/PyClickerRPA.egg-info/PKG-INFO +138 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/PyClickerRPA.egg-info/SOURCES.txt +27 -1
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/PyClickerRPA.egg-info/entry_points.txt +2 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/PyClickerRPA.egg-info/requires.txt +8 -0
- pyclickerrpa-0.3.0/README.md +100 -0
- pyclickerrpa-0.3.0/autoclicker/adb.py +163 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/capture.py +26 -10
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/flow_connection.py +8 -1
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/flow_editor.py +194 -67
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/flow_node.py +57 -8
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/main_window.py +177 -33
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/task_widget.py +55 -4
- pyclickerrpa-0.3.0/autoclicker/gui/testkit_widget.py +167 -0
- pyclickerrpa-0.3.0/autoclicker/matcher.py +262 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/models.py +53 -1
- pyclickerrpa-0.3.0/autoclicker/ocr.py +107 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/run.py +11 -2
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/runner.py +233 -29
- pyclickerrpa-0.3.0/autoclicker/testkit/__init__.py +25 -0
- pyclickerrpa-0.3.0/autoclicker/testkit/__main__.py +51 -0
- pyclickerrpa-0.3.0/autoclicker/testkit/assertions.py +256 -0
- pyclickerrpa-0.3.0/autoclicker/testkit/case.py +130 -0
- pyclickerrpa-0.3.0/autoclicker/testkit/loader.py +82 -0
- pyclickerrpa-0.3.0/autoclicker/testkit/pytest_plugin.py +100 -0
- pyclickerrpa-0.3.0/autoclicker/testkit/report.py +124 -0
- pyclickerrpa-0.3.0/autoclicker/testkit/runner.py +147 -0
- pyclickerrpa-0.3.0/autoclicker/testkit/visual.py +87 -0
- pyclickerrpa-0.3.0/autoclicker/web/__init__.py +4 -0
- pyclickerrpa-0.3.0/autoclicker/web/__main__.py +33 -0
- pyclickerrpa-0.3.0/autoclicker/web/run_manager.py +181 -0
- pyclickerrpa-0.3.0/autoclicker/web/scheduler.py +73 -0
- pyclickerrpa-0.3.0/autoclicker/web/serialize.py +40 -0
- pyclickerrpa-0.3.0/autoclicker/web/server.py +178 -0
- pyclickerrpa-0.3.0/autoclicker/web/static/app.js +244 -0
- pyclickerrpa-0.3.0/autoclicker/web/static/index.html +58 -0
- pyclickerrpa-0.3.0/autoclicker/web/static/style.css +61 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/windows.py +24 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/pyproject.toml +17 -3
- pyclickerrpa-0.3.0/tests/test_adb.py +122 -0
- pyclickerrpa-0.3.0/tests/test_flow_branch.py +146 -0
- pyclickerrpa-0.3.0/tests/test_matcher.py +140 -0
- pyclickerrpa-0.3.0/tests/test_runner.py +450 -0
- pyclickerrpa-0.3.0/tests/test_testkit.py +303 -0
- pyclickerrpa-0.3.0/tests/test_testkit_gui.py +48 -0
- pyclickerrpa-0.3.0/tests/test_web.py +155 -0
- pyclickerrpa-0.1.0/PKG-INFO +0 -90
- pyclickerrpa-0.1.0/PyClickerRPA.egg-info/PKG-INFO +0 -90
- pyclickerrpa-0.1.0/README.md +0 -66
- pyclickerrpa-0.1.0/autoclicker/matcher.py +0 -152
- pyclickerrpa-0.1.0/autoclicker/ocr.py +0 -63
- pyclickerrpa-0.1.0/tests/test_matcher.py +0 -64
- pyclickerrpa-0.1.0/tests/test_runner.py +0 -196
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/PyClickerRPA.egg-info/dependency_links.txt +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/PyClickerRPA.egg-info/top_level.txt +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/__init__.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/app.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/clicker.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/dpi.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/__init__.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/hotkeys.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/monitor.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/preview_label.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/qt_runner.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/recorder.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/scheduler_widget.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/autoclicker/gui/snippet_library.py +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/setup.cfg +0 -0
- {pyclickerrpa-0.1.0 → pyclickerrpa-0.3.0}/tests/test_models.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Equinox
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyClickerRPA
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Visual UI automation & testing tool: Windows + Android(ADB) automation, robust/self-healing image matching, OCR, node flow editor, recorder, a visual test framework (assertions, visual regression, HTML/JUnit reports) and a web console.
|
|
5
|
+
Author: Equinox
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Equinox/PyClickerRPA
|
|
8
|
+
Keywords: autoclicker,rpa,automation,opencv,template-matching,ocr,gui,windows,android,adb,testing,visual-regression,ui-testing
|
|
9
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
10
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Desktop Environment
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy>=1.23
|
|
24
|
+
Requires-Dist: opencv-python>=4.8
|
|
25
|
+
Requires-Dist: pywin32>=305; sys_platform == "win32"
|
|
26
|
+
Requires-Dist: pynput>=1.7
|
|
27
|
+
Requires-Dist: PySide6>=6.5
|
|
28
|
+
Provides-Extra: ocr
|
|
29
|
+
Requires-Dist: paddleocr>=2.7; extra == "ocr"
|
|
30
|
+
Requires-Dist: paddlepaddle>=2.5; extra == "ocr"
|
|
31
|
+
Provides-Extra: web
|
|
32
|
+
Requires-Dist: fastapi>=0.110; extra == "web"
|
|
33
|
+
Requires-Dist: uvicorn[standard]>=0.27; extra == "web"
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
36
|
+
Requires-Dist: httpx>=0.24; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# PyClickerRPA
|
|
40
|
+
|
|
41
|
+
A visual UI **automation & testing** tool. Automate Windows apps *and* Android
|
|
42
|
+
devices/emulators by what's on screen — image/text matching + mouse/keyboard (or
|
|
43
|
+
ADB) input — then turn those flows into a **visual test framework** with
|
|
44
|
+
assertions, visual-regression baselines, HTML/JUnit reports, and a **web console**
|
|
45
|
+
for remote runs.
|
|
46
|
+
|
|
47
|
+
Python/PySide6, started as a port of a Qt/C++ AutoClicker (still reads/writes the
|
|
48
|
+
same `.json` task format).
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
pip install PyClickerRPA
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Optional extras:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
pip install "PyClickerRPA[ocr]" # PaddleOCR — find-text / read-text steps
|
|
60
|
+
pip install "PyClickerRPA[web]" # FastAPI + uvicorn — web console
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Entry points
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
autoclicker # desktop GUI (flow editor / list editor / test tab)
|
|
67
|
+
autoclicker-run task.json --window "Notepad" # headless run against a window
|
|
68
|
+
autoclicker-run task.json --adb # run against an Android device (adb)
|
|
69
|
+
autoclicker-testkit suite.json --report out.html --junit out.xml # run a test suite
|
|
70
|
+
autoclicker-web --suites-dir examples # web console at http://127.0.0.1:8000
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Automation engine
|
|
74
|
+
|
|
75
|
+
- **Targets**: any visible window, the whole screen (multi-monitor + high-DPI
|
|
76
|
+
aware), or an **Android device/emulator via ADB** (`--adb [serial]`).
|
|
77
|
+
- **Image matching**: OpenCV template match with **self-healing** by default —
|
|
78
|
+
template → multi-scale → **ORB feature match** fallback (survives scaling /
|
|
79
|
+
minor UI changes); **transparent-mask** templates (PNG alpha) ignore
|
|
80
|
+
don't-care regions. Per-step `matchMode` = `robust` / `template` / `feature`.
|
|
81
|
+
- **Also**: pixel-color checks, optional PaddleOCR text find / read-to-variable.
|
|
82
|
+
- **Smart wait**: `wait_stable` waits until the screen (or an ROI) stops
|
|
83
|
+
changing — replaces guessed `sleep`s (fast when ready, patient when slow).
|
|
84
|
+
- **~30+ step types**: click image/text/position, click-all, relative/offset
|
|
85
|
+
clicks, wait / random / wait-stable, drag / long-press / pinch / scroll,
|
|
86
|
+
key / type / hotkey, loop / goto / while / do-until, if image/text/color/
|
|
87
|
+
count/var with **true/false branching** (if-else), set-var, read-text→variable,
|
|
88
|
+
activate-window, HTTP request, run-Python-script, call sub-task, groups, and a
|
|
89
|
+
**stop** step to end a run — with retry / repeat / until policies.
|
|
90
|
+
|
|
91
|
+
## Authoring (desktop GUI)
|
|
92
|
+
|
|
93
|
+
Drag-and-drop **node flow graph** (condition nodes have **true/false branch
|
|
94
|
+
ports** for visual if-else; a terminal **stop** node ends the flow) and an
|
|
95
|
+
interoperable **step list** with a full
|
|
96
|
+
per-step dialog; an action **recorder** (global hooks), snippet library, task
|
|
97
|
+
packages (`.zip` with templates), on-preview coordinate/color picking, template
|
|
98
|
+
crop, single-step test run; global hotkeys (F6 run/stop, ESC stop), scheduler,
|
|
99
|
+
and an image/color **monitor/trigger** with tray icon; a **Test** tab to load and
|
|
100
|
+
run visual test suites.
|
|
101
|
+
|
|
102
|
+
## Visual test framework (`autoclicker.testkit`)
|
|
103
|
+
|
|
104
|
+
Turn automation into tests:
|
|
105
|
+
|
|
106
|
+
- **Assertions**: image / text / color present-or-absent, variable comparison.
|
|
107
|
+
- **Visual regression**: baseline diff (first run captures the baseline; later
|
|
108
|
+
runs flag changed-pixel ratio and emit a baseline|actual|heatmap image).
|
|
109
|
+
- **Cases & suites**: declarative JSON suites *or* pytest fixtures (`screen`,
|
|
110
|
+
`visual`); **data-driven** cases (a data table / CSV runs the case per row).
|
|
111
|
+
- **Reports**: self-contained **HTML** (embedded screenshots + per-step
|
|
112
|
+
timeline, optional GIF) and **JUnit XML** for CI; retries mark **flaky**.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
# pytest style
|
|
116
|
+
def test_login(screen, visual):
|
|
117
|
+
screen.assert_image("assets/login_btn.png")
|
|
118
|
+
screen.assert_text("Username")
|
|
119
|
+
visual.check("login.png", tolerance=0.02)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Web console (`autoclicker-web`)
|
|
123
|
+
|
|
124
|
+
Browser UI to trigger suites (single or batched), watch **live per-line logs**
|
|
125
|
+
and per-case results, schedule recurring runs, list ADB devices and grab
|
|
126
|
+
**screenshots**, browse **persisted run history**, and open the HTML report — all
|
|
127
|
+
served by FastAPI with a zero-build vanilla-JS front end.
|
|
128
|
+
|
|
129
|
+
## Notes
|
|
130
|
+
|
|
131
|
+
- Host is **Windows** (Win32 capture + `SendInput`); ADB targets are
|
|
132
|
+
cross-platform via the `adb` CLI (must be on `PATH`).
|
|
133
|
+
- OCR's first run downloads PaddleOCR models (~100 MB).
|
|
134
|
+
- Some target windows may require running as administrator.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyClickerRPA
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Visual UI automation & testing tool: Windows + Android(ADB) automation, robust/self-healing image matching, OCR, node flow editor, recorder, a visual test framework (assertions, visual regression, HTML/JUnit reports) and a web console.
|
|
5
|
+
Author: Equinox
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Equinox/PyClickerRPA
|
|
8
|
+
Keywords: autoclicker,rpa,automation,opencv,template-matching,ocr,gui,windows,android,adb,testing,visual-regression,ui-testing
|
|
9
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
10
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Desktop Environment
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy>=1.23
|
|
24
|
+
Requires-Dist: opencv-python>=4.8
|
|
25
|
+
Requires-Dist: pywin32>=305; sys_platform == "win32"
|
|
26
|
+
Requires-Dist: pynput>=1.7
|
|
27
|
+
Requires-Dist: PySide6>=6.5
|
|
28
|
+
Provides-Extra: ocr
|
|
29
|
+
Requires-Dist: paddleocr>=2.7; extra == "ocr"
|
|
30
|
+
Requires-Dist: paddlepaddle>=2.5; extra == "ocr"
|
|
31
|
+
Provides-Extra: web
|
|
32
|
+
Requires-Dist: fastapi>=0.110; extra == "web"
|
|
33
|
+
Requires-Dist: uvicorn[standard]>=0.27; extra == "web"
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
36
|
+
Requires-Dist: httpx>=0.24; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# PyClickerRPA
|
|
40
|
+
|
|
41
|
+
A visual UI **automation & testing** tool. Automate Windows apps *and* Android
|
|
42
|
+
devices/emulators by what's on screen — image/text matching + mouse/keyboard (or
|
|
43
|
+
ADB) input — then turn those flows into a **visual test framework** with
|
|
44
|
+
assertions, visual-regression baselines, HTML/JUnit reports, and a **web console**
|
|
45
|
+
for remote runs.
|
|
46
|
+
|
|
47
|
+
Python/PySide6, started as a port of a Qt/C++ AutoClicker (still reads/writes the
|
|
48
|
+
same `.json` task format).
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
pip install PyClickerRPA
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Optional extras:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
pip install "PyClickerRPA[ocr]" # PaddleOCR — find-text / read-text steps
|
|
60
|
+
pip install "PyClickerRPA[web]" # FastAPI + uvicorn — web console
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Entry points
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
autoclicker # desktop GUI (flow editor / list editor / test tab)
|
|
67
|
+
autoclicker-run task.json --window "Notepad" # headless run against a window
|
|
68
|
+
autoclicker-run task.json --adb # run against an Android device (adb)
|
|
69
|
+
autoclicker-testkit suite.json --report out.html --junit out.xml # run a test suite
|
|
70
|
+
autoclicker-web --suites-dir examples # web console at http://127.0.0.1:8000
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Automation engine
|
|
74
|
+
|
|
75
|
+
- **Targets**: any visible window, the whole screen (multi-monitor + high-DPI
|
|
76
|
+
aware), or an **Android device/emulator via ADB** (`--adb [serial]`).
|
|
77
|
+
- **Image matching**: OpenCV template match with **self-healing** by default —
|
|
78
|
+
template → multi-scale → **ORB feature match** fallback (survives scaling /
|
|
79
|
+
minor UI changes); **transparent-mask** templates (PNG alpha) ignore
|
|
80
|
+
don't-care regions. Per-step `matchMode` = `robust` / `template` / `feature`.
|
|
81
|
+
- **Also**: pixel-color checks, optional PaddleOCR text find / read-to-variable.
|
|
82
|
+
- **Smart wait**: `wait_stable` waits until the screen (or an ROI) stops
|
|
83
|
+
changing — replaces guessed `sleep`s (fast when ready, patient when slow).
|
|
84
|
+
- **~30+ step types**: click image/text/position, click-all, relative/offset
|
|
85
|
+
clicks, wait / random / wait-stable, drag / long-press / pinch / scroll,
|
|
86
|
+
key / type / hotkey, loop / goto / while / do-until, if image/text/color/
|
|
87
|
+
count/var with **true/false branching** (if-else), set-var, read-text→variable,
|
|
88
|
+
activate-window, HTTP request, run-Python-script, call sub-task, groups, and a
|
|
89
|
+
**stop** step to end a run — with retry / repeat / until policies.
|
|
90
|
+
|
|
91
|
+
## Authoring (desktop GUI)
|
|
92
|
+
|
|
93
|
+
Drag-and-drop **node flow graph** (condition nodes have **true/false branch
|
|
94
|
+
ports** for visual if-else; a terminal **stop** node ends the flow) and an
|
|
95
|
+
interoperable **step list** with a full
|
|
96
|
+
per-step dialog; an action **recorder** (global hooks), snippet library, task
|
|
97
|
+
packages (`.zip` with templates), on-preview coordinate/color picking, template
|
|
98
|
+
crop, single-step test run; global hotkeys (F6 run/stop, ESC stop), scheduler,
|
|
99
|
+
and an image/color **monitor/trigger** with tray icon; a **Test** tab to load and
|
|
100
|
+
run visual test suites.
|
|
101
|
+
|
|
102
|
+
## Visual test framework (`autoclicker.testkit`)
|
|
103
|
+
|
|
104
|
+
Turn automation into tests:
|
|
105
|
+
|
|
106
|
+
- **Assertions**: image / text / color present-or-absent, variable comparison.
|
|
107
|
+
- **Visual regression**: baseline diff (first run captures the baseline; later
|
|
108
|
+
runs flag changed-pixel ratio and emit a baseline|actual|heatmap image).
|
|
109
|
+
- **Cases & suites**: declarative JSON suites *or* pytest fixtures (`screen`,
|
|
110
|
+
`visual`); **data-driven** cases (a data table / CSV runs the case per row).
|
|
111
|
+
- **Reports**: self-contained **HTML** (embedded screenshots + per-step
|
|
112
|
+
timeline, optional GIF) and **JUnit XML** for CI; retries mark **flaky**.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
# pytest style
|
|
116
|
+
def test_login(screen, visual):
|
|
117
|
+
screen.assert_image("assets/login_btn.png")
|
|
118
|
+
screen.assert_text("Username")
|
|
119
|
+
visual.check("login.png", tolerance=0.02)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Web console (`autoclicker-web`)
|
|
123
|
+
|
|
124
|
+
Browser UI to trigger suites (single or batched), watch **live per-line logs**
|
|
125
|
+
and per-case results, schedule recurring runs, list ADB devices and grab
|
|
126
|
+
**screenshots**, browse **persisted run history**, and open the HTML report — all
|
|
127
|
+
served by FastAPI with a zero-build vanilla-JS front end.
|
|
128
|
+
|
|
129
|
+
## Notes
|
|
130
|
+
|
|
131
|
+
- Host is **Windows** (Win32 capture + `SendInput`); ADB targets are
|
|
132
|
+
cross-platform via the `adb` CLI (must be on `PATH`).
|
|
133
|
+
- OCR's first run downloads PaddleOCR models (~100 MB).
|
|
134
|
+
- Some target windows may require running as administrator.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
LICENSE
|
|
1
2
|
README.md
|
|
2
3
|
pyproject.toml
|
|
3
4
|
PyClickerRPA.egg-info/PKG-INFO
|
|
@@ -7,6 +8,7 @@ PyClickerRPA.egg-info/entry_points.txt
|
|
|
7
8
|
PyClickerRPA.egg-info/requires.txt
|
|
8
9
|
PyClickerRPA.egg-info/top_level.txt
|
|
9
10
|
autoclicker/__init__.py
|
|
11
|
+
autoclicker/adb.py
|
|
10
12
|
autoclicker/app.py
|
|
11
13
|
autoclicker/capture.py
|
|
12
14
|
autoclicker/clicker.py
|
|
@@ -30,6 +32,30 @@ autoclicker/gui/recorder.py
|
|
|
30
32
|
autoclicker/gui/scheduler_widget.py
|
|
31
33
|
autoclicker/gui/snippet_library.py
|
|
32
34
|
autoclicker/gui/task_widget.py
|
|
35
|
+
autoclicker/gui/testkit_widget.py
|
|
36
|
+
autoclicker/testkit/__init__.py
|
|
37
|
+
autoclicker/testkit/__main__.py
|
|
38
|
+
autoclicker/testkit/assertions.py
|
|
39
|
+
autoclicker/testkit/case.py
|
|
40
|
+
autoclicker/testkit/loader.py
|
|
41
|
+
autoclicker/testkit/pytest_plugin.py
|
|
42
|
+
autoclicker/testkit/report.py
|
|
43
|
+
autoclicker/testkit/runner.py
|
|
44
|
+
autoclicker/testkit/visual.py
|
|
45
|
+
autoclicker/web/__init__.py
|
|
46
|
+
autoclicker/web/__main__.py
|
|
47
|
+
autoclicker/web/run_manager.py
|
|
48
|
+
autoclicker/web/scheduler.py
|
|
49
|
+
autoclicker/web/serialize.py
|
|
50
|
+
autoclicker/web/server.py
|
|
51
|
+
autoclicker/web/static/app.js
|
|
52
|
+
autoclicker/web/static/index.html
|
|
53
|
+
autoclicker/web/static/style.css
|
|
54
|
+
tests/test_adb.py
|
|
55
|
+
tests/test_flow_branch.py
|
|
33
56
|
tests/test_matcher.py
|
|
34
57
|
tests/test_models.py
|
|
35
|
-
tests/test_runner.py
|
|
58
|
+
tests/test_runner.py
|
|
59
|
+
tests/test_testkit.py
|
|
60
|
+
tests/test_testkit_gui.py
|
|
61
|
+
tests/test_web.py
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# PyClickerRPA
|
|
2
|
+
|
|
3
|
+
A visual UI **automation & testing** tool. Automate Windows apps *and* Android
|
|
4
|
+
devices/emulators by what's on screen — image/text matching + mouse/keyboard (or
|
|
5
|
+
ADB) input — then turn those flows into a **visual test framework** with
|
|
6
|
+
assertions, visual-regression baselines, HTML/JUnit reports, and a **web console**
|
|
7
|
+
for remote runs.
|
|
8
|
+
|
|
9
|
+
Python/PySide6, started as a port of a Qt/C++ AutoClicker (still reads/writes the
|
|
10
|
+
same `.json` task format).
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
pip install PyClickerRPA
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Optional extras:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
pip install "PyClickerRPA[ocr]" # PaddleOCR — find-text / read-text steps
|
|
22
|
+
pip install "PyClickerRPA[web]" # FastAPI + uvicorn — web console
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Entry points
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
autoclicker # desktop GUI (flow editor / list editor / test tab)
|
|
29
|
+
autoclicker-run task.json --window "Notepad" # headless run against a window
|
|
30
|
+
autoclicker-run task.json --adb # run against an Android device (adb)
|
|
31
|
+
autoclicker-testkit suite.json --report out.html --junit out.xml # run a test suite
|
|
32
|
+
autoclicker-web --suites-dir examples # web console at http://127.0.0.1:8000
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Automation engine
|
|
36
|
+
|
|
37
|
+
- **Targets**: any visible window, the whole screen (multi-monitor + high-DPI
|
|
38
|
+
aware), or an **Android device/emulator via ADB** (`--adb [serial]`).
|
|
39
|
+
- **Image matching**: OpenCV template match with **self-healing** by default —
|
|
40
|
+
template → multi-scale → **ORB feature match** fallback (survives scaling /
|
|
41
|
+
minor UI changes); **transparent-mask** templates (PNG alpha) ignore
|
|
42
|
+
don't-care regions. Per-step `matchMode` = `robust` / `template` / `feature`.
|
|
43
|
+
- **Also**: pixel-color checks, optional PaddleOCR text find / read-to-variable.
|
|
44
|
+
- **Smart wait**: `wait_stable` waits until the screen (or an ROI) stops
|
|
45
|
+
changing — replaces guessed `sleep`s (fast when ready, patient when slow).
|
|
46
|
+
- **~30+ step types**: click image/text/position, click-all, relative/offset
|
|
47
|
+
clicks, wait / random / wait-stable, drag / long-press / pinch / scroll,
|
|
48
|
+
key / type / hotkey, loop / goto / while / do-until, if image/text/color/
|
|
49
|
+
count/var with **true/false branching** (if-else), set-var, read-text→variable,
|
|
50
|
+
activate-window, HTTP request, run-Python-script, call sub-task, groups, and a
|
|
51
|
+
**stop** step to end a run — with retry / repeat / until policies.
|
|
52
|
+
|
|
53
|
+
## Authoring (desktop GUI)
|
|
54
|
+
|
|
55
|
+
Drag-and-drop **node flow graph** (condition nodes have **true/false branch
|
|
56
|
+
ports** for visual if-else; a terminal **stop** node ends the flow) and an
|
|
57
|
+
interoperable **step list** with a full
|
|
58
|
+
per-step dialog; an action **recorder** (global hooks), snippet library, task
|
|
59
|
+
packages (`.zip` with templates), on-preview coordinate/color picking, template
|
|
60
|
+
crop, single-step test run; global hotkeys (F6 run/stop, ESC stop), scheduler,
|
|
61
|
+
and an image/color **monitor/trigger** with tray icon; a **Test** tab to load and
|
|
62
|
+
run visual test suites.
|
|
63
|
+
|
|
64
|
+
## Visual test framework (`autoclicker.testkit`)
|
|
65
|
+
|
|
66
|
+
Turn automation into tests:
|
|
67
|
+
|
|
68
|
+
- **Assertions**: image / text / color present-or-absent, variable comparison.
|
|
69
|
+
- **Visual regression**: baseline diff (first run captures the baseline; later
|
|
70
|
+
runs flag changed-pixel ratio and emit a baseline|actual|heatmap image).
|
|
71
|
+
- **Cases & suites**: declarative JSON suites *or* pytest fixtures (`screen`,
|
|
72
|
+
`visual`); **data-driven** cases (a data table / CSV runs the case per row).
|
|
73
|
+
- **Reports**: self-contained **HTML** (embedded screenshots + per-step
|
|
74
|
+
timeline, optional GIF) and **JUnit XML** for CI; retries mark **flaky**.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# pytest style
|
|
78
|
+
def test_login(screen, visual):
|
|
79
|
+
screen.assert_image("assets/login_btn.png")
|
|
80
|
+
screen.assert_text("Username")
|
|
81
|
+
visual.check("login.png", tolerance=0.02)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Web console (`autoclicker-web`)
|
|
85
|
+
|
|
86
|
+
Browser UI to trigger suites (single or batched), watch **live per-line logs**
|
|
87
|
+
and per-case results, schedule recurring runs, list ADB devices and grab
|
|
88
|
+
**screenshots**, browse **persisted run history**, and open the HTML report — all
|
|
89
|
+
served by FastAPI with a zero-build vanilla-JS front end.
|
|
90
|
+
|
|
91
|
+
## Notes
|
|
92
|
+
|
|
93
|
+
- Host is **Windows** (Win32 capture + `SendInput`); ADB targets are
|
|
94
|
+
cross-platform via the `adb` CLI (must be on `PATH`).
|
|
95
|
+
- OCR's first run downloads PaddleOCR models (~100 MB).
|
|
96
|
+
- Some target windows may require running as administrator.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""ADB 设备后端:自动化 Android 手机/模拟器(通用 adb CLI)。
|
|
2
|
+
|
|
3
|
+
契约与 Windows 后端一致(capture 返回 BGR ndarray、clicker 首参 hwnd 但忽略),
|
|
4
|
+
所以能直接注入 TaskRunner(target_hwnd=None, capture=AdbCapture, clicker=AdbClicker)。
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
# Windows VK -> Android keycode(覆盖常用键;其余忽略)
|
|
13
|
+
_VK_TO_ANDROID = {
|
|
14
|
+
0x0D: 66, # ENTER
|
|
15
|
+
0x1B: 4, # ESC -> BACK
|
|
16
|
+
0x09: 61, # TAB
|
|
17
|
+
0x20: 62, # SPACE
|
|
18
|
+
0x08: 67, # BACKSPACE -> DEL
|
|
19
|
+
0x2E: 112, # DELETE -> FORWARD_DEL
|
|
20
|
+
0x26: 19, 0x28: 20, 0x25: 21, 0x27: 22, # UP DOWN LEFT RIGHT
|
|
21
|
+
0x24: 3, # HOME
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _vk_to_android(vk: int) -> int | None:
|
|
26
|
+
if vk in _VK_TO_ANDROID:
|
|
27
|
+
return _VK_TO_ANDROID[vk]
|
|
28
|
+
if 0x41 <= vk <= 0x5A: # A-Z -> KEYCODE_A(29)..
|
|
29
|
+
return 29 + (vk - 0x41)
|
|
30
|
+
if 0x30 <= vk <= 0x39: # 0-9 -> KEYCODE_0(7)..
|
|
31
|
+
return 7 + (vk - 0x30)
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AdbDevice:
|
|
36
|
+
def __init__(self, serial: str | None = None, adb: str = "adb"):
|
|
37
|
+
self.serial = serial
|
|
38
|
+
self.adb = adb
|
|
39
|
+
|
|
40
|
+
def _cmd(self, args: list[str]) -> list[str]:
|
|
41
|
+
base = [self.adb]
|
|
42
|
+
if self.serial:
|
|
43
|
+
base += ["-s", self.serial]
|
|
44
|
+
return base + args
|
|
45
|
+
|
|
46
|
+
def _run(self, args: list[str]) -> bytes:
|
|
47
|
+
try:
|
|
48
|
+
proc = subprocess.run(self._cmd(args), stdout=subprocess.PIPE,
|
|
49
|
+
stderr=subprocess.PIPE)
|
|
50
|
+
except FileNotFoundError as e:
|
|
51
|
+
raise RuntimeError(f"找不到 adb 可执行文件 '{self.adb}',请安装 Android Platform Tools 并加入 PATH") from e
|
|
52
|
+
if proc.returncode != 0:
|
|
53
|
+
raise RuntimeError(f"adb {' '.join(args)} 失败: {proc.stderr.decode('utf-8', 'replace').strip()}")
|
|
54
|
+
return proc.stdout
|
|
55
|
+
|
|
56
|
+
# ---- capture ----
|
|
57
|
+
def screencap(self) -> np.ndarray:
|
|
58
|
+
"""截图 -> BGR ndarray(exec-out 避免 stdout CRLF 转换的坑)。"""
|
|
59
|
+
import cv2
|
|
60
|
+
raw = self._run(["exec-out", "screencap", "-p"])
|
|
61
|
+
img = cv2.imdecode(np.frombuffer(raw, dtype=np.uint8), cv2.IMREAD_COLOR)
|
|
62
|
+
if img is None or img.size == 0:
|
|
63
|
+
raise RuntimeError("screencap 解码失败(设备可能不支持 exec-out)")
|
|
64
|
+
return img
|
|
65
|
+
|
|
66
|
+
# ---- input ----
|
|
67
|
+
def tap(self, x: int, y: int) -> None:
|
|
68
|
+
self._run(["shell", "input", "tap", str(int(x)), str(int(y))])
|
|
69
|
+
|
|
70
|
+
def swipe(self, x1: int, y1: int, x2: int, y2: int, ms: int = 300) -> None:
|
|
71
|
+
self._run(["shell", "input", "swipe", str(int(x1)), str(int(y1)),
|
|
72
|
+
str(int(x2)), str(int(y2)), str(int(ms))])
|
|
73
|
+
|
|
74
|
+
def long_press(self, x: int, y: int, hold_ms: int = 800) -> None:
|
|
75
|
+
self.swipe(x, y, x, y, hold_ms) # same point + duration = long press
|
|
76
|
+
|
|
77
|
+
def text(self, s: str) -> None:
|
|
78
|
+
# Android `input text` 用 %s 表示空格
|
|
79
|
+
self._run(["shell", "input", "text", s.replace(" ", "%s")])
|
|
80
|
+
|
|
81
|
+
def keyevent(self, code: int) -> None:
|
|
82
|
+
self._run(["shell", "input", "keyevent", str(int(code))])
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def list_devices(adb: str = "adb") -> list[str]:
|
|
86
|
+
try:
|
|
87
|
+
out = subprocess.run([adb, "devices"], stdout=subprocess.PIPE,
|
|
88
|
+
stderr=subprocess.PIPE).stdout.decode("utf-8", "replace")
|
|
89
|
+
except FileNotFoundError:
|
|
90
|
+
return []
|
|
91
|
+
serials = []
|
|
92
|
+
for line in out.splitlines()[1:]:
|
|
93
|
+
parts = line.split()
|
|
94
|
+
if len(parts) >= 2 and parts[1] == "device":
|
|
95
|
+
serials.append(parts[0])
|
|
96
|
+
return serials
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class AdbCapture:
|
|
100
|
+
"""capture 后端:忽略 hwnd,一律截设备屏。"""
|
|
101
|
+
def __init__(self, device: AdbDevice):
|
|
102
|
+
self.device = device
|
|
103
|
+
|
|
104
|
+
def capture_screen(self) -> np.ndarray:
|
|
105
|
+
return self.device.screencap()
|
|
106
|
+
|
|
107
|
+
def capture_window(self, hwnd) -> np.ndarray:
|
|
108
|
+
return self.device.screencap()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class AdbClicker:
|
|
112
|
+
"""clicker 后端:把 Windows 语义的调用映射到 adb input(首参 hwnd 忽略)。"""
|
|
113
|
+
def __init__(self, device: AdbDevice):
|
|
114
|
+
self.device = device
|
|
115
|
+
|
|
116
|
+
def click(self, hwnd, x: int, y: int) -> None:
|
|
117
|
+
self.device.tap(x, y)
|
|
118
|
+
|
|
119
|
+
def double_click(self, hwnd, x: int, y: int) -> None:
|
|
120
|
+
self.device.tap(x, y)
|
|
121
|
+
self.device.tap(x, y)
|
|
122
|
+
|
|
123
|
+
def right_click(self, hwnd, x: int, y: int) -> None:
|
|
124
|
+
self.device.tap(x, y) # Android 无右键,退化为点击
|
|
125
|
+
|
|
126
|
+
def long_press(self, hwnd, x: int, y: int, hold_ms: int) -> None:
|
|
127
|
+
self.device.long_press(x, y, hold_ms)
|
|
128
|
+
|
|
129
|
+
def swipe(self, hwnd, x1, y1, x2, y2, duration_ms: int = 300) -> None:
|
|
130
|
+
self.device.swipe(x1, y1, x2, y2, duration_ms)
|
|
131
|
+
|
|
132
|
+
def drag(self, hwnd, x1, y1, x2, y2, duration_ms: int = 300) -> None:
|
|
133
|
+
self.device.swipe(x1, y1, x2, y2, duration_ms)
|
|
134
|
+
|
|
135
|
+
def scroll(self, hwnd, x, y, amount: int) -> None:
|
|
136
|
+
# 正数向上滚:从 (x,y) 向上滑
|
|
137
|
+
self.device.swipe(x, y, x, y - amount * 100, 300)
|
|
138
|
+
|
|
139
|
+
def type_text(self, text: str) -> None:
|
|
140
|
+
self.device.text(text)
|
|
141
|
+
|
|
142
|
+
def key_press(self, vk: int) -> None:
|
|
143
|
+
code = _vk_to_android(vk)
|
|
144
|
+
if code is not None:
|
|
145
|
+
self.device.keyevent(code)
|
|
146
|
+
|
|
147
|
+
def key_down(self, vk: int) -> None:
|
|
148
|
+
self.key_press(vk)
|
|
149
|
+
|
|
150
|
+
def key_up(self, vk: int) -> None:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
def hotkey(self, modifier: int, vk: int) -> None:
|
|
154
|
+
self.key_press(vk) # Android input 无组合键,尽力而为
|
|
155
|
+
|
|
156
|
+
def pinch(self, hwnd, cx, cy, start_dist, end_dist, duration_ms: int = 400) -> None:
|
|
157
|
+
pass # adb input 不支持多点触控,跳过
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def make_adb_backends(serial: str | None = None, adb: str = "adb"):
|
|
161
|
+
"""便捷构造:返回 (capture, clicker) 供注入 TaskRunner。"""
|
|
162
|
+
dev = AdbDevice(serial, adb)
|
|
163
|
+
return AdbCapture(dev), AdbClicker(dev)
|