@reactiive/ennio 0.0.1
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.
- package/EnnioCore.podspec +61 -0
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/android/CMakeLists.txt +40 -0
- package/android/build.gradle +64 -0
- package/cpp/ElementMatcher.cpp +661 -0
- package/cpp/ElementMatcher.hpp +244 -0
- package/cpp/EnnioLog.hpp +182 -0
- package/cpp/HybridEnnio.cpp +1161 -0
- package/cpp/HybridEnnio.hpp +174 -0
- package/cpp/IdleMonitor.hpp +277 -0
- package/cpp/Protocol.cpp +135 -0
- package/cpp/Protocol.hpp +47 -0
- package/cpp/SelectorCriteria.hpp +281 -0
- package/cpp/SelectorParser.cpp +649 -0
- package/cpp/SelectorParser.hpp +94 -0
- package/cpp/ShadowTreeTraverser.cpp +305 -0
- package/cpp/ShadowTreeTraverser.hpp +142 -0
- package/cpp/TestIDRegistry.cpp +109 -0
- package/cpp/TestIDRegistry.hpp +84 -0
- package/dist/cli.js +16221 -0
- package/ios/EnnioAutoInit.mm +338 -0
- package/ios/EnnioDebugBanner.h +19 -0
- package/ios/EnnioDebugBanner.mm +178 -0
- package/ios/EnnioRuntimeHelper.h +264 -0
- package/ios/EnnioRuntimeHelper.mm +2443 -0
- package/lib/Ennio.nitro.d.ts +263 -0
- package/lib/Ennio.nitro.d.ts.map +1 -0
- package/lib/Ennio.nitro.js +2 -0
- package/lib/Ennio.nitro.js.map +1 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +45 -0
- package/lib/index.js.map +1 -0
- package/nitro.json +24 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/EnnioCore+autolinking.cmake +81 -0
- package/nitrogen/generated/android/EnnioCore+autolinking.gradle +27 -0
- package/nitrogen/generated/android/EnnioCoreOnLoad.cpp +49 -0
- package/nitrogen/generated/android/EnnioCoreOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/ennio/EnnioCoreOnLoad.kt +35 -0
- package/nitrogen/generated/ios/EnnioCore+autolinking.rb +62 -0
- package/nitrogen/generated/ios/EnnioCore-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/EnnioCore-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/EnnioCore-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/EnnioCoreAutolinking.mm +35 -0
- package/nitrogen/generated/ios/EnnioCoreAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/ExtendedElementInfo.hpp +118 -0
- package/nitrogen/generated/shared/c++/HybridEnnioSpec.cpp +44 -0
- package/nitrogen/generated/shared/c++/HybridEnnioSpec.hpp +93 -0
- package/nitrogen/generated/shared/c++/LayoutMetrics.hpp +103 -0
- package/nitrogen/generated/shared/c++/ScrollDirection.hpp +84 -0
- package/package.json +78 -0
- package/react-native.config.js +14 -0
- package/src/Ennio.nitro.ts +363 -0
- package/src/cli/hid-daemon.py +129 -0
- package/src/index.ts +72 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
//
|
|
2
|
+
// EnnioRuntimeHelper.h
|
|
3
|
+
// Provides access to React Native runtime and UIManager for Ennio
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
#pragma once
|
|
7
|
+
|
|
8
|
+
#ifdef __OBJC__
|
|
9
|
+
@class RCTSurfacePresenter;
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
#ifdef __cplusplus
|
|
13
|
+
|
|
14
|
+
#include <memory>
|
|
15
|
+
#include <string>
|
|
16
|
+
#include <vector>
|
|
17
|
+
|
|
18
|
+
namespace facebook {
|
|
19
|
+
namespace react {
|
|
20
|
+
class UIManager;
|
|
21
|
+
class ShadowNode;
|
|
22
|
+
} // namespace react
|
|
23
|
+
} // namespace facebook
|
|
24
|
+
|
|
25
|
+
namespace ennio {
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* EnnioRuntimeHelper - Singleton for accessing React Native's UIManager
|
|
29
|
+
*
|
|
30
|
+
* This class bridges iOS's RCTSurfacePresenter to C++ code that needs
|
|
31
|
+
* access to the shadow tree.
|
|
32
|
+
*/
|
|
33
|
+
class EnnioRuntimeHelper {
|
|
34
|
+
public:
|
|
35
|
+
static EnnioRuntimeHelper& getInstance();
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Set the surface presenter (called from Objective-C during app startup)
|
|
39
|
+
*/
|
|
40
|
+
void setSurfacePresenter(void* surfacePresenter);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get the UIManager for shadow tree access
|
|
44
|
+
* Returns nullptr if not yet initialized
|
|
45
|
+
*/
|
|
46
|
+
std::shared_ptr<facebook::react::UIManager> getUIManager();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get the current shadow tree root for the default surface
|
|
50
|
+
*/
|
|
51
|
+
std::shared_ptr<const facebook::react::ShadowNode> getShadowTreeRoot();
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if the helper is properly initialized
|
|
55
|
+
*/
|
|
56
|
+
bool isInitialized() const;
|
|
57
|
+
|
|
58
|
+
// ============================================
|
|
59
|
+
// Alert/Modal Handling
|
|
60
|
+
// ============================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if an alert is currently presented
|
|
64
|
+
*/
|
|
65
|
+
bool isAlertPresent();
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the text content of the current alert (title + message)
|
|
69
|
+
*/
|
|
70
|
+
std::string getAlertText();
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the list of button titles in the current alert
|
|
74
|
+
*/
|
|
75
|
+
std::vector<std::string> getAlertButtons();
|
|
76
|
+
|
|
77
|
+
// ============================================
|
|
78
|
+
// Fast-mode Writes
|
|
79
|
+
//
|
|
80
|
+
// Each method finds a UIView via accessibilityIdentifier (RN testID)
|
|
81
|
+
// and drives the action directly through UIKit / iOS accessibility
|
|
82
|
+
// APIs. No XCUI helper, no synthetic touches. Per-call latency is
|
|
83
|
+
// dominated by the view-tree DFS (~5ms on the example app); no
|
|
84
|
+
// cross-process IPC, no animation race.
|
|
85
|
+
// ============================================
|
|
86
|
+
|
|
87
|
+
bool tap(const std::string& testID);
|
|
88
|
+
bool tapByLabel(const std::string& text);
|
|
89
|
+
/**
|
|
90
|
+
* Tap at an absolute window-coordinate point (logical pt, not pixels).
|
|
91
|
+
* Synthesises a UITouch sequence so UIKit's hit-test routes the
|
|
92
|
+
* gesture through the responder chain. Used by tapBySelector when a
|
|
93
|
+
* shadow-tree match resolves to a layout instead of a UIView with a
|
|
94
|
+
* testID.
|
|
95
|
+
*/
|
|
96
|
+
bool tapAtScreenPoint(double x, double y);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Prepare a tap by testID: stable-coord poll + auto-scroll
|
|
100
|
+
* fallback + UIMenu detection, all in a single JSI call.
|
|
101
|
+
* Replaces the CLI-side `layoutCenter` polling loop — saves
|
|
102
|
+
* ~5-10 CDP round trips per tap. Returns JSON object
|
|
103
|
+
* `{"x":..,"y":..,"isMenu":..}` on success, empty string on
|
|
104
|
+
* failure. CLI runs the actual tap via idb HID so RNGH-wrapped
|
|
105
|
+
* components (pressto's PressableScale, RNBetterTapGestureRecognizer)
|
|
106
|
+
* see real CoreSimulator touch events.
|
|
107
|
+
*/
|
|
108
|
+
std::string prepareTap(const std::string& testID, double screenW, double screenH);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Synthesise a pan gesture from (x1,y1) to (x2,y2) over `durationMs`.
|
|
112
|
+
* Fast path when the start point hits a UIScrollView ancestor:
|
|
113
|
+
* setContentOffset with the delta, no UITouch tax. Otherwise drives
|
|
114
|
+
* a UITouchPhaseMoved loop along the line. Window-coords (logical pt).
|
|
115
|
+
* Sim-only.
|
|
116
|
+
*/
|
|
117
|
+
bool swipeAtPoints(double x1, double y1, double x2, double y2, double durationMs);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Synthesise a hardware key press by HID keycode against the current
|
|
121
|
+
* first responder when it conforms to UIKeyInput. Mapped: 42=backspace,
|
|
122
|
+
* 40=return, 44=space. Returns false when no input field is focused.
|
|
123
|
+
*/
|
|
124
|
+
bool pressHardwareKey(double keyCode);
|
|
125
|
+
/**
|
|
126
|
+
* Origin of the React surface inside the user app's window, in
|
|
127
|
+
* logical points. Adds to Fabric's surface-relative screenX/screenY
|
|
128
|
+
* to get window-relative coords idb can tap on.
|
|
129
|
+
*/
|
|
130
|
+
std::pair<double, double> getSurfaceOffset();
|
|
131
|
+
/**
|
|
132
|
+
* Logical-point bounds of the key UIWindow. (width, height) — origin
|
|
133
|
+
* is always (0,0). Used by tap-time visibility gates: a view whose
|
|
134
|
+
* window-frame falls fully outside this rect is unreachable by a
|
|
135
|
+
* real finger and should fail the tap.
|
|
136
|
+
*/
|
|
137
|
+
std::pair<double, double> getKeyWindowSize();
|
|
138
|
+
/**
|
|
139
|
+
* Returns true iff the testID-bearing view exists, has non-zero size,
|
|
140
|
+
* and overlaps the key window's visible bounds. Mirrors what a real
|
|
141
|
+
* finger could reach: an offscreen / not-yet-laid-out element returns
|
|
142
|
+
* false. Skips the gesture / responder tree — purely a layout check.
|
|
143
|
+
*/
|
|
144
|
+
bool isViewOnscreen(const std::string& testID);
|
|
145
|
+
/**
|
|
146
|
+
* True iff the testID's UIView is in the iOS accessibility tree.
|
|
147
|
+
* The predicate: view is attached to a window AND no ancestor has
|
|
148
|
+
* accessibilityElementsHidden=YES. This is what XCUI uses to decide
|
|
149
|
+
* whether an element is enumerable. Used to filter shadow-tree
|
|
150
|
+
* selector matches that resolved to a stale UIView under an inactive
|
|
151
|
+
* tab / pushed stack frame / occluded modal host.
|
|
152
|
+
*/
|
|
153
|
+
bool isInA11yTree(const std::string& testID);
|
|
154
|
+
/**
|
|
155
|
+
* Window-relative frame of the UIView with the given testID. Returns
|
|
156
|
+
* (x, y, w, h) all in logical points. Width/height = 0 when not
|
|
157
|
+
* found. This bypasses Fabric's surface-relative layout entirely:
|
|
158
|
+
* the value already accounts for ScrollView contentInsetAdjustment,
|
|
159
|
+
* safe-area padding, and any other UIKit-runtime offsets.
|
|
160
|
+
*/
|
|
161
|
+
std::tuple<double, double, double, double> getViewWindowFrame(const std::string& testID);
|
|
162
|
+
/**
|
|
163
|
+
* Window-relative frame for the first UIView whose accessibilityLabel
|
|
164
|
+
* matches `text` (substring). Used for native widgets that don't show
|
|
165
|
+
* up in the Fabric tree — UITabBar items, alert buttons, system
|
|
166
|
+
* sheets, RNScreens stack headers.
|
|
167
|
+
*/
|
|
168
|
+
std::tuple<double, double, double, double> getViewWindowFrameByLabel(const std::string& text);
|
|
169
|
+
/**
|
|
170
|
+
* True if the testID's UIView (or any ancestor) is a UIButton with
|
|
171
|
+
* `menu` set + `showsMenuAsPrimaryAction` (zeego DropdownMenu,
|
|
172
|
+
* react-native-ios-context-menu). Such triggers cannot be opened by
|
|
173
|
+
* UIControl.sendActions or programmatic _presentMenuAtLocation: in
|
|
174
|
+
* the host app — only by real HID input. Caller routes through idb.
|
|
175
|
+
*/
|
|
176
|
+
bool isMenuTriggerAncestor(const std::string& testID);
|
|
177
|
+
/**
|
|
178
|
+
* Wipe the app's sandbox: Library/, Documents/, tmp/. Runs in-process
|
|
179
|
+
* via NSFileManager so it works identically on Simulator and on a
|
|
180
|
+
* real device — no host filesystem access required, no shell-out.
|
|
181
|
+
* Caller should restart the app afterwards to drop in-memory state
|
|
182
|
+
* (Zustand stores, RN HermesRuntime caches, etc.).
|
|
183
|
+
* Keychain is a separate iOS subsystem — see clearKeychain().
|
|
184
|
+
*/
|
|
185
|
+
bool clearAppDataDirectories();
|
|
186
|
+
bool doubleTap(const std::string& testID);
|
|
187
|
+
bool longPress(const std::string& testID, int durationMs);
|
|
188
|
+
bool typeText(const std::string& testID, const std::string& text);
|
|
189
|
+
bool clearText(const std::string& testID);
|
|
190
|
+
bool eraseText(const std::string& testID, int count);
|
|
191
|
+
bool pressKey(const std::string& testID, const std::string& keyName);
|
|
192
|
+
bool scroll(const std::string& testID, const std::string& direction, double distance);
|
|
193
|
+
bool swipe(const std::string& testID, const std::string& direction, double distance);
|
|
194
|
+
bool scrollTo(const std::string& scrollViewTestID, const std::string& elementTestID);
|
|
195
|
+
bool tapTab(int index);
|
|
196
|
+
/**
|
|
197
|
+
* Find a UITabBarController in any window scene and select the tab
|
|
198
|
+
* whose viewController's title (or tabBarItem.title) matches `name`
|
|
199
|
+
* case-insensitively. Used when NativeTabs renders bar items via
|
|
200
|
+
* SwiftUI / UIKit hosts that don't surface their UIView subtree to
|
|
201
|
+
* accessibility-label walks. Returns false if no matching tab.
|
|
202
|
+
*/
|
|
203
|
+
bool tapTabByName(const std::string& name);
|
|
204
|
+
/**
|
|
205
|
+
* One-shot tap-readiness query. Returns the window-coord frame for
|
|
206
|
+
* `testID` only after iOS is in a state where the next touch will
|
|
207
|
+
* actually deliver: no UIPresentationController / UINavigation
|
|
208
|
+
* transition is in flight, no alert-level UIWindow is sitting on
|
|
209
|
+
* top, the target's userInteractionEnabled chain is clean. Polls
|
|
210
|
+
* in-process every ~20 ms up to `maxWaitMs`; the JS / CLI side pays
|
|
211
|
+
* a single WS round-trip regardless of wait duration. Returns
|
|
212
|
+
* (0,0,0,0) on timeout. Library-agnostic — works for Pressable,
|
|
213
|
+
* TouchableOpacity, RNGH BaseButton, pressto, anything that hangs
|
|
214
|
+
* its handler off iOS's responder + recognizer pipeline.
|
|
215
|
+
*/
|
|
216
|
+
std::tuple<double, double, double, double> getReadyCoord(const std::string& testID, int maxWaitMs);
|
|
217
|
+
/**
|
|
218
|
+
* Library-agnostic tap that bypasses iOS HID + gesture coordinator.
|
|
219
|
+
* Cascades through three direct-fire paths against the testID's
|
|
220
|
+
* UIView:
|
|
221
|
+
* 1. nearest enabled UIControl ancestor → sendActions touchUpInside
|
|
222
|
+
* (UIButton, UISwitch, RNGestureHandlerButton)
|
|
223
|
+
* 2. gestureRecognizers on view + ancestors driven by direct
|
|
224
|
+
* touchesBegan: / touchesEnded: calls (RCTSurfaceTouchHandler →
|
|
225
|
+
* RN responder release → Pressable/Touchable onPress;
|
|
226
|
+
* RNNativeViewGestureRecognizer → RNGH onActivated → user onPress)
|
|
227
|
+
* 3. accessibilityActivate (a11y-tagged buttons)
|
|
228
|
+
* Returns true if any path fired. Caller falls back to idb HID on
|
|
229
|
+
* false (covers pure-native UIAlertController buttons et al).
|
|
230
|
+
*/
|
|
231
|
+
bool fireTapByTestID(const std::string& testID);
|
|
232
|
+
bool backGesture();
|
|
233
|
+
bool hideKeyboard();
|
|
234
|
+
bool tapAlertButton(const std::string& buttonText);
|
|
235
|
+
bool dismissAlert();
|
|
236
|
+
bool copyToClipboard(const std::string& text);
|
|
237
|
+
bool pasteFromClipboard(const std::string& testID);
|
|
238
|
+
std::string getClipboardText();
|
|
239
|
+
|
|
240
|
+
private:
|
|
241
|
+
EnnioRuntimeHelper() = default;
|
|
242
|
+
~EnnioRuntimeHelper() = default;
|
|
243
|
+
EnnioRuntimeHelper(const EnnioRuntimeHelper&) = delete;
|
|
244
|
+
EnnioRuntimeHelper& operator=(const EnnioRuntimeHelper&) = delete;
|
|
245
|
+
|
|
246
|
+
void* surfacePresenter_ = nullptr;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
} // namespace ennio
|
|
250
|
+
|
|
251
|
+
#endif // __cplusplus
|
|
252
|
+
|
|
253
|
+
// C function for Objective-C code to call
|
|
254
|
+
#ifdef __cplusplus
|
|
255
|
+
extern "C" {
|
|
256
|
+
#endif
|
|
257
|
+
|
|
258
|
+
#ifdef __OBJC__
|
|
259
|
+
void EnnioSetSurfacePresenter(RCTSurfacePresenter* _Nonnull presenter);
|
|
260
|
+
#endif
|
|
261
|
+
|
|
262
|
+
#ifdef __cplusplus
|
|
263
|
+
}
|
|
264
|
+
#endif
|