@oniroproject/core 0.12.0 → 0.13.0
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/README.md +10 -4
- package/dist/index.cjs +425 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +209 -4
- package/dist/index.d.ts +209 -4
- package/dist/index.js +409 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -206,17 +206,23 @@ The injection-safe primitives every device operation builds on (`spawn` with `sh
|
|
|
206
206
|
| `takeScreenshot` | `(ScreenshotOptions) => Promise<Screenshot>` | `{ pixels: Buffer, width, height }` — raw JPEG, no decode. |
|
|
207
207
|
| `captureBurst` | `(CaptureBurstOptions) => Promise<Buffer[]>` | N frames `intervalMs` apart, pulled back in order. |
|
|
208
208
|
| `getDisplaySize` | `(config, opts?) => Promise<DisplaySize \| null>` | Render resolution from `hidumper` (no image work). |
|
|
209
|
-
| `dumpLayout` | `(DumpLayoutOptions) => Promise<DumpLayoutResult>` | `uitest dumpLayout` → pruned, 0–1-normalized `LayoutNode` tree. |
|
|
210
|
-
| `parseBounds` / `pruneLayout` | — | Pure helpers behind `dumpLayout`. |
|
|
209
|
+
| `dumpLayout` | `(DumpLayoutOptions) => Promise<DumpLayoutResult>` | `uitest dumpLayout` → pruned, 0–1-normalized `LayoutNode` tree, with `hint`, `disabled`, and `covered` (painted over by a later filled layer). `detectKeyboard` adds `keyboard: KeyboardState`. |
|
|
210
|
+
| `parseBounds` / `pruneLayout` / `findCoveredNodes` | — | Pure helpers behind `dumpLayout`. |
|
|
211
|
+
| `flattenLayout` / `findLayoutNodes` / `pickTarget` | — | Pure: flatten a tree (nodes tagged with their `window`), match a `LayoutSelector` (`text` / `id` / `type`), and choose one uncovered, preferably clickable node (throws `TargetNotFoundError` with `reason` `none` / `covered` / `ambiguous`). |
|
|
212
|
+
| `getImeBundles` / `parseImeBundles` / `findKeyboardWindow` | — | Installed input methods (`hidumper -s 3703`) and the soft-keyboard window in a layout. |
|
|
211
213
|
|
|
212
214
|
### hdc — input injection
|
|
213
215
|
|
|
214
216
|
| Export | Signature | Description |
|
|
215
217
|
| --- | --- | --- |
|
|
216
|
-
| `sendInput` | `(SendInputOptions) => Promise<void>` | One `uitest uiInput` action in **pixel** space (`InputType`: click/doubleClick/longClick/swipe/drag/fling/keyEvent/inputText). |
|
|
218
|
+
| `sendInput` | `(SendInputOptions) => Promise<void>` | One `uitest uiInput` action in **pixel** space (`InputType`: click/doubleClick/longClick/swipe/drag/fling/keyEvent/inputText). `key` accepts names and combos (see `resolveKeyEvent`). |
|
|
219
|
+
| `performInput` | `(PerformInputOptions) => Promise<PerformInputResult>` | `sendInput` plus a start point from an element selector (`target`), 0–1 fractions, or pixels; `clearText`, `inputText` with `clear`, and keyboard-aware `hideKeyboard`. |
|
|
220
|
+
| `locateTarget` | `(LocateTargetOptions) => Promise<LocatedTarget>` | Resolve a selector against the live layout to one element and its pixel center, dismissing the keyboard if it hides the element. |
|
|
221
|
+
| `hideKeyboard` | `(opts) => Promise<boolean>` | Press Back only when the soft keyboard is shown. |
|
|
222
|
+
| `waitForTarget` | `(WaitForTargetOptions) => Promise<FlatLayoutNode \| null>` | Poll the layout until an uncovered element matches (or, with `gone`, until none does). |
|
|
217
223
|
| `sendGesture` | `(SendGestureOptions) => Promise<void>` | Multi-`Waypoint` path; routes through `uinput` when `holdStartMs`/`holdEndMs` need real press timing. |
|
|
218
224
|
| `sendRawTouch` | `(SendRawTouchOptions) => Promise<void>` | Low-level `uinput -T` down/move/up events. |
|
|
219
|
-
| `buildInputCommand` / `buildGestureCommands` / `buildRawTouchCommand` | — | Pure command builders (unit-testable). |
|
|
225
|
+
| `buildInputCommand` / `buildGestureCommands` / `buildRawTouchCommand` / `resolveKeyEvent` | — | Pure command builders (unit-testable). |
|
|
220
226
|
|
|
221
227
|
### hdc — logging (hilog)
|
|
222
228
|
|
package/dist/index.cjs
CHANGED
|
@@ -47,9 +47,11 @@ __export(index_exports, {
|
|
|
47
47
|
SIGNING_ERRORS: () => SIGNING_ERRORS,
|
|
48
48
|
SdkNotInstalledError: () => SdkNotInstalledError,
|
|
49
49
|
TMP_DIR_HINT: () => TMP_DIR_HINT,
|
|
50
|
+
TargetNotFoundError: () => TargetNotFoundError,
|
|
50
51
|
UnsupportedPlatformError: () => UnsupportedPlatformError,
|
|
51
52
|
VERSION: () => VERSION,
|
|
52
53
|
applyChanges: () => applyChanges,
|
|
54
|
+
assertLayoutSelector: () => assertLayoutSelector,
|
|
53
55
|
attemptHdcConnection: () => attemptHdcConnection,
|
|
54
56
|
buildCmdWrapper: () => buildCmdWrapper,
|
|
55
57
|
buildGestureCommands: () => buildGestureCommands,
|
|
@@ -72,6 +74,7 @@ __export(index_exports, {
|
|
|
72
74
|
dedupEntries: () => dedupEntries,
|
|
73
75
|
defaultContext: () => defaultContext,
|
|
74
76
|
defaultPaths: () => defaultPaths,
|
|
77
|
+
describeLayoutNode: () => describeLayoutNode,
|
|
75
78
|
detectAaStartFailure: () => detectAaStartFailure,
|
|
76
79
|
detectProjectSdkVersion: () => detectProjectSdkVersion,
|
|
77
80
|
detectRuntimeOs: () => detectRuntimeOs,
|
|
@@ -97,8 +100,12 @@ __export(index_exports, {
|
|
|
97
100
|
extractZipWithProgress: () => extractZipWithProgress,
|
|
98
101
|
findAppProcessId: () => findAppProcessId,
|
|
99
102
|
findCmdToolsSourceDir: () => findCmdToolsSourceDir,
|
|
103
|
+
findCoveredNodes: () => findCoveredNodes,
|
|
100
104
|
findHarmonyOsSdk: () => findHarmonyOsSdk,
|
|
105
|
+
findKeyboardWindow: () => findKeyboardWindow,
|
|
106
|
+
findLayoutNodes: () => findLayoutNodes,
|
|
101
107
|
findRunningProcess: () => findRunningProcess,
|
|
108
|
+
flattenLayout: () => flattenLayout,
|
|
102
109
|
forceStop: () => forceStop,
|
|
103
110
|
formatBytes: () => formatBytes,
|
|
104
111
|
generateSigningConfigs: () => generateSigningConfigs,
|
|
@@ -113,6 +120,7 @@ __export(index_exports, {
|
|
|
113
120
|
getFreeSpaceBytes: () => getFreeSpaceBytes,
|
|
114
121
|
getHdcPath: () => getHdcPath,
|
|
115
122
|
getHvigorwPath: () => getHvigorwPath,
|
|
123
|
+
getImeBundles: () => getImeBundles,
|
|
116
124
|
getInstalledSdks: () => getInstalledSdks,
|
|
117
125
|
getKey: () => getKey,
|
|
118
126
|
getMainAbility: () => getMainAbility,
|
|
@@ -125,6 +133,7 @@ __export(index_exports, {
|
|
|
125
133
|
harmonyOsAutoSign: () => harmonyOsAutoSign,
|
|
126
134
|
harmonyOsSdkLabelForApi: () => harmonyOsSdkLabelForApi,
|
|
127
135
|
hdcExec: () => hdcExec,
|
|
136
|
+
hideKeyboard: () => hideKeyboard,
|
|
128
137
|
installApp: () => installApp,
|
|
129
138
|
installCmdTools: () => installCmdTools,
|
|
130
139
|
installEmulator: () => installEmulator,
|
|
@@ -141,6 +150,7 @@ __export(index_exports, {
|
|
|
141
150
|
listModules: () => listModules,
|
|
142
151
|
listRunningProcesses: () => listRunningProcesses,
|
|
143
152
|
listTemplates: () => listTemplates,
|
|
153
|
+
locateTarget: () => locateTarget,
|
|
144
154
|
needsCmdWrapper: () => needsCmdWrapper,
|
|
145
155
|
nonInteractivePrompter: () => nonInteractivePrompter,
|
|
146
156
|
noopLogger: () => noopLogger,
|
|
@@ -151,9 +161,12 @@ __export(index_exports, {
|
|
|
151
161
|
parseCodelinterFindings: () => parseCodelinterFindings,
|
|
152
162
|
parseDeviceList: () => parseDeviceList,
|
|
153
163
|
parseHilogLine: () => parseHilogLine,
|
|
164
|
+
parseImeBundles: () => parseImeBundles,
|
|
154
165
|
parseMainAbilityFromBmDump: () => parseMainAbilityFromBmDump,
|
|
155
166
|
parseRemoteExit: () => parseRemoteExit,
|
|
167
|
+
performInput: () => performInput,
|
|
156
168
|
pickSigningKind: () => pickSigningKind,
|
|
169
|
+
pickTarget: () => pickTarget,
|
|
157
170
|
prepareSigning: () => prepareSigning,
|
|
158
171
|
pruneLayout: () => pruneLayout,
|
|
159
172
|
readJson5File: () => readJson5File,
|
|
@@ -166,6 +179,7 @@ __export(index_exports, {
|
|
|
166
179
|
removeSdk: () => removeSdk,
|
|
167
180
|
removeTempWorkDir: () => removeTempWorkDir,
|
|
168
181
|
requireHarmonyOsSdk: () => requireHarmonyOsSdk,
|
|
182
|
+
resolveKeyEvent: () => resolveKeyEvent,
|
|
169
183
|
resolveTmpRoot: () => resolveTmpRoot,
|
|
170
184
|
runCodelinter: () => runCodelinter,
|
|
171
185
|
runHvigorw: () => runHvigorw,
|
|
@@ -195,8 +209,10 @@ __export(index_exports, {
|
|
|
195
209
|
waitForBundle: () => waitForBundle,
|
|
196
210
|
waitForCondition: () => waitForCondition,
|
|
197
211
|
waitForLog: () => waitForLog,
|
|
212
|
+
waitForTarget: () => waitForTarget,
|
|
198
213
|
watchLog: () => watchLog,
|
|
199
214
|
waypointsToRawEvents: () => waypointsToRawEvents,
|
|
215
|
+
withoutWindow: () => withoutWindow,
|
|
200
216
|
writeJson5File: () => writeJson5File,
|
|
201
217
|
writeJsonFile: () => writeJsonFile
|
|
202
218
|
});
|
|
@@ -2619,6 +2635,55 @@ async function captureBurst(opts) {
|
|
|
2619
2635
|
}
|
|
2620
2636
|
|
|
2621
2637
|
// src/hdc/input.ts
|
|
2638
|
+
var UITEST_KEY_NAMES = ["Back", "Home", "Power"];
|
|
2639
|
+
var KEY_IDS = {
|
|
2640
|
+
back: 2,
|
|
2641
|
+
home: 1,
|
|
2642
|
+
power: 18,
|
|
2643
|
+
volumeup: 16,
|
|
2644
|
+
volumedown: 17,
|
|
2645
|
+
up: 2012,
|
|
2646
|
+
down: 2013,
|
|
2647
|
+
left: 2014,
|
|
2648
|
+
right: 2015,
|
|
2649
|
+
alt: 2045,
|
|
2650
|
+
shift: 2047,
|
|
2651
|
+
tab: 2049,
|
|
2652
|
+
space: 2050,
|
|
2653
|
+
enter: 2054,
|
|
2654
|
+
delete: 2055,
|
|
2655
|
+
backspace: 2055,
|
|
2656
|
+
pageup: 2068,
|
|
2657
|
+
pagedown: 2069,
|
|
2658
|
+
escape: 2070,
|
|
2659
|
+
esc: 2070,
|
|
2660
|
+
forwarddelete: 2071,
|
|
2661
|
+
ctrl: 2072,
|
|
2662
|
+
meta: 2076,
|
|
2663
|
+
movehome: 2081,
|
|
2664
|
+
moveend: 2082
|
|
2665
|
+
};
|
|
2666
|
+
function keyId(name) {
|
|
2667
|
+
const k = name.trim().toLowerCase();
|
|
2668
|
+
if (/^\d+$/.test(k)) return Number(k);
|
|
2669
|
+
if (/^[a-z]$/.test(k)) return 2017 + (k.charCodeAt(0) - 97);
|
|
2670
|
+
const id = KEY_IDS[k];
|
|
2671
|
+
if (id === void 0) {
|
|
2672
|
+
throw new OniroError(
|
|
2673
|
+
`Unknown key "${name}". Use a numeric KeyCode id, a letter, or one of: ${Object.keys(KEY_IDS).join(", ")}. Combine up to three with "+", e.g. Ctrl+A.`
|
|
2674
|
+
);
|
|
2675
|
+
}
|
|
2676
|
+
return id;
|
|
2677
|
+
}
|
|
2678
|
+
function resolveKeyEvent(key) {
|
|
2679
|
+
const spec = String(key).trim();
|
|
2680
|
+
if (spec === "") throw new OniroError('send input "keyEvent" requires a key.');
|
|
2681
|
+
const native = UITEST_KEY_NAMES.find((n) => n.toLowerCase() === spec.toLowerCase());
|
|
2682
|
+
if (native) return native;
|
|
2683
|
+
const parts = spec.includes("+") ? spec.split("+") : spec.split(/\s+/);
|
|
2684
|
+
if (parts.length > 3) throw new OniroError(`uitest injects at most three keys at once; got "${spec}".`);
|
|
2685
|
+
return parts.map(keyId).join(" ");
|
|
2686
|
+
}
|
|
2622
2687
|
function reqNum(value, name, type) {
|
|
2623
2688
|
if (value === void 0 || Number.isNaN(value)) {
|
|
2624
2689
|
throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
|
|
@@ -2639,7 +2704,7 @@ function buildInputCommand(opts) {
|
|
|
2639
2704
|
}
|
|
2640
2705
|
case "keyEvent":
|
|
2641
2706
|
if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
|
|
2642
|
-
return `uitest uiInput keyEvent ${opts.key}`;
|
|
2707
|
+
return `uitest uiInput keyEvent ${resolveKeyEvent(opts.key)}`;
|
|
2643
2708
|
case "inputText": {
|
|
2644
2709
|
const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
|
|
2645
2710
|
if (opts.pxX !== void 0 && opts.pxY !== void 0) {
|
|
@@ -2737,6 +2802,48 @@ var fs20 = __toESM(require("fs"), 1);
|
|
|
2737
2802
|
var os10 = __toESM(require("os"), 1);
|
|
2738
2803
|
var path20 = __toESM(require("path"), 1);
|
|
2739
2804
|
var import_node_crypto2 = require("crypto");
|
|
2805
|
+
|
|
2806
|
+
// src/hdc/ime.ts
|
|
2807
|
+
var INPUT_METHOD_SA_ID = 3703;
|
|
2808
|
+
var IME_BUNDLE_FALLBACK = /inputmethod|keyboard|(^|\.)ime(\.|$)/i;
|
|
2809
|
+
function parseImeBundles(output) {
|
|
2810
|
+
const current = [];
|
|
2811
|
+
const others = [];
|
|
2812
|
+
const entry = /\{[^{}]*"ime"\s*:\s*"([^"/]+)\/[^"]*"[^{}]*\}/g;
|
|
2813
|
+
for (const m of output.matchAll(entry)) {
|
|
2814
|
+
const bundle = m[1];
|
|
2815
|
+
const list = /"isCurrentIme"\s*:\s*"true"/.test(m[0]) ? current : others;
|
|
2816
|
+
if (!current.includes(bundle) && !others.includes(bundle)) list.push(bundle);
|
|
2817
|
+
}
|
|
2818
|
+
return [...current, ...others];
|
|
2819
|
+
}
|
|
2820
|
+
async function getImeBundles(config, opts = {}) {
|
|
2821
|
+
try {
|
|
2822
|
+
const res = await shell({
|
|
2823
|
+
config,
|
|
2824
|
+
command: `hidumper -s ${INPUT_METHOD_SA_ID} -a '-a'`,
|
|
2825
|
+
deviceSerial: opts.deviceSerial,
|
|
2826
|
+
timeoutMs: 1e4,
|
|
2827
|
+
logger: opts.logger
|
|
2828
|
+
});
|
|
2829
|
+
if (res.code === 0) return parseImeBundles(res.stdout);
|
|
2830
|
+
} catch (err) {
|
|
2831
|
+
opts.logger?.debug(`could not list input methods: ${err instanceof Error ? err.message : String(err)}`);
|
|
2832
|
+
}
|
|
2833
|
+
return [];
|
|
2834
|
+
}
|
|
2835
|
+
function findKeyboardWindow(tree, imeBundles) {
|
|
2836
|
+
const isIme = (bundle) => imeBundles.length > 0 ? imeBundles.includes(bundle) : IME_BUNDLE_FALLBACK.test(bundle);
|
|
2837
|
+
const stack = [tree];
|
|
2838
|
+
while (stack.length > 0) {
|
|
2839
|
+
const n = stack.shift();
|
|
2840
|
+
if (n.bundle && isIme(n.bundle)) return n;
|
|
2841
|
+
stack.push(...n.children ?? []);
|
|
2842
|
+
}
|
|
2843
|
+
return null;
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/hdc/dump.ts
|
|
2740
2847
|
function parseBounds(s) {
|
|
2741
2848
|
const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
|
|
2742
2849
|
if (!m) return null;
|
|
@@ -2771,7 +2878,46 @@ var FLAG_MAP = [
|
|
|
2771
2878
|
["scrollable", "scroll"],
|
|
2772
2879
|
["longClickable", "longClick"]
|
|
2773
2880
|
];
|
|
2774
|
-
function
|
|
2881
|
+
function colorAlpha(color) {
|
|
2882
|
+
const m = /^#([0-9a-f]{2})?[0-9a-f]{6}$/i.exec(color ?? "");
|
|
2883
|
+
if (!m) return 0;
|
|
2884
|
+
return m[1] === void 0 ? 255 : parseInt(m[1], 16);
|
|
2885
|
+
}
|
|
2886
|
+
function paintsFill(a) {
|
|
2887
|
+
if (a.visible === "false") return false;
|
|
2888
|
+
if (a.opacity !== void 0 && a.opacity !== "" && !(Number(a.opacity) > 0)) return false;
|
|
2889
|
+
return colorAlpha(a.backgroundColor) > 0;
|
|
2890
|
+
}
|
|
2891
|
+
function findCoveredNodes(root) {
|
|
2892
|
+
const order = [];
|
|
2893
|
+
const fills = [];
|
|
2894
|
+
const visit = (n) => {
|
|
2895
|
+
const a = n.attributes ?? {};
|
|
2896
|
+
const entry = { node: n, bounds: parseBounds(a.bounds), end: 0 };
|
|
2897
|
+
if (entry.bounds && paintsFill(a)) fills.push(order.length);
|
|
2898
|
+
order.push(entry);
|
|
2899
|
+
const kids = (n.children ?? []).map((c, i) => ({ c, i, z: Number(c.attributes?.zIndex) || 0 })).sort((p, q) => p.z - q.z || p.i - q.i);
|
|
2900
|
+
for (const k of kids) visit(k.c);
|
|
2901
|
+
entry.end = order.length;
|
|
2902
|
+
};
|
|
2903
|
+
visit(root);
|
|
2904
|
+
const covered = /* @__PURE__ */ new Set();
|
|
2905
|
+
for (const p of order) {
|
|
2906
|
+
if (!p.bounds) continue;
|
|
2907
|
+
const cx = (p.bounds[0] + p.bounds[2]) / 2;
|
|
2908
|
+
const cy = (p.bounds[1] + p.bounds[3]) / 2;
|
|
2909
|
+
for (const j of fills) {
|
|
2910
|
+
if (j < p.end) continue;
|
|
2911
|
+
const q = order[j].bounds;
|
|
2912
|
+
if (cx >= q[0] && cx < q[2] && cy >= q[1] && cy < q[3]) {
|
|
2913
|
+
covered.add(p.node);
|
|
2914
|
+
break;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
return covered;
|
|
2919
|
+
}
|
|
2920
|
+
function pruneLayout(node, display, covered) {
|
|
2775
2921
|
const a = node.attributes ?? {};
|
|
2776
2922
|
const px = parseBounds(a.bounds);
|
|
2777
2923
|
const out = {};
|
|
@@ -2789,28 +2935,125 @@ function pruneLayout(node, display) {
|
|
|
2789
2935
|
if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
|
|
2790
2936
|
if (a.text) out.text = a.text;
|
|
2791
2937
|
if (a.description) out.desc = a.description;
|
|
2938
|
+
if (a.hint) out.hint = a.hint;
|
|
2792
2939
|
if (a.id) out.id = a.id;
|
|
2793
2940
|
if (a.key && a.key !== a.id) out.key = a.key;
|
|
2794
2941
|
if (a.bundleName) out.bundle = a.bundleName;
|
|
2795
2942
|
for (const [attr, short] of FLAG_MAP) {
|
|
2796
2943
|
if (a[attr] === "true") out[short] = true;
|
|
2797
2944
|
}
|
|
2945
|
+
if (a.enabled === "false") out.disabled = true;
|
|
2946
|
+
if (covered?.has(node)) out.covered = true;
|
|
2798
2947
|
const children = [];
|
|
2799
2948
|
for (const c of node.children ?? []) {
|
|
2800
|
-
const pc = pruneLayout(c, display);
|
|
2949
|
+
const pc = pruneLayout(c, display, covered);
|
|
2801
2950
|
if (pc) {
|
|
2802
2951
|
if (Array.isArray(pc)) children.push(...pc);
|
|
2803
2952
|
else children.push(pc);
|
|
2804
2953
|
}
|
|
2805
2954
|
}
|
|
2806
2955
|
const hasSignal = Boolean(
|
|
2807
|
-
out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
|
|
2956
|
+
out.text || out.desc || out.hint || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
|
|
2808
2957
|
);
|
|
2809
2958
|
if (!hasSignal && children.length === 0) return null;
|
|
2810
2959
|
if (!hasSignal) return children.length === 1 ? children[0] : children;
|
|
2811
2960
|
if (children.length) out.children = children;
|
|
2812
2961
|
return out;
|
|
2813
2962
|
}
|
|
2963
|
+
function flattenLayout(tree) {
|
|
2964
|
+
const out = [];
|
|
2965
|
+
const visit = (n, window) => {
|
|
2966
|
+
const { children, ...rest } = n;
|
|
2967
|
+
const win = n.bundle ?? window;
|
|
2968
|
+
if (Object.keys(rest).length > 0) out.push({ ...rest, ...win ? { window: win } : {} });
|
|
2969
|
+
for (const c of children ?? []) visit(c, win);
|
|
2970
|
+
};
|
|
2971
|
+
visit(tree, void 0);
|
|
2972
|
+
return out;
|
|
2973
|
+
}
|
|
2974
|
+
function assertLayoutSelector(selector) {
|
|
2975
|
+
if (selector.text === void 0 && selector.id === void 0 && selector.type === void 0) {
|
|
2976
|
+
throw new OniroError("A layout selector needs a text, id, or type.");
|
|
2977
|
+
}
|
|
2978
|
+
if (selector.text !== void 0 && selector.text.trim() === "") {
|
|
2979
|
+
throw new OniroError("A layout selector text must not be blank.");
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
function withoutWindow(tree, bundle) {
|
|
2983
|
+
const children = (tree.children ?? []).filter((c) => c.bundle !== bundle).map((c) => withoutWindow(c, bundle));
|
|
2984
|
+
return tree.children ? { ...tree, children } : tree;
|
|
2985
|
+
}
|
|
2986
|
+
function findLayoutNodes(tree, selector) {
|
|
2987
|
+
assertLayoutSelector(selector);
|
|
2988
|
+
const type = selector.type?.toLowerCase();
|
|
2989
|
+
const nodes = flattenLayout(tree).filter(
|
|
2990
|
+
(n) => (type === void 0 || n.type?.toLowerCase() === type) && (selector.id === void 0 || n.id === selector.id || n.key === selector.id)
|
|
2991
|
+
);
|
|
2992
|
+
if (selector.text === void 0) return nodes;
|
|
2993
|
+
const want = selector.text.trim().toLowerCase();
|
|
2994
|
+
const labels = (n) => [n.text, n.desc, n.hint].filter((s) => !!s).map((s) => s.trim().toLowerCase());
|
|
2995
|
+
const exact = nodes.filter((n) => labels(n).some((l) => l === want));
|
|
2996
|
+
if (exact.length > 0) return exact;
|
|
2997
|
+
return nodes.filter((n) => labels(n).some((l) => l.includes(want)));
|
|
2998
|
+
}
|
|
2999
|
+
function describeSelector(s) {
|
|
3000
|
+
const parts = [];
|
|
3001
|
+
if (s.text !== void 0) parts.push(`text "${s.text}"`);
|
|
3002
|
+
if (s.id !== void 0) parts.push(`id "${s.id}"`);
|
|
3003
|
+
if (s.type !== void 0) parts.push(`type ${s.type}`);
|
|
3004
|
+
return parts.join(", ");
|
|
3005
|
+
}
|
|
3006
|
+
function describeLayoutNode(n) {
|
|
3007
|
+
const label = n.text ?? n.desc ?? (n.hint !== void 0 ? `hint: ${n.hint}` : void 0);
|
|
3008
|
+
const at = n.c ? ` at ${n.c[0].toFixed(3)},${n.c[1].toFixed(3)}` : "";
|
|
3009
|
+
return `${n.type ?? "node"}${label !== void 0 ? ` "${label}"` : ""}${n.id ? ` #${n.id}` : ""}${at}`;
|
|
3010
|
+
}
|
|
3011
|
+
var area = (b) => b ? (b[2] - b[0]) * (b[3] - b[1]) : 0;
|
|
3012
|
+
function enclosesTarget(outer, inner) {
|
|
3013
|
+
const b = outer.b;
|
|
3014
|
+
const c = inner.c;
|
|
3015
|
+
if (!b || !c) return false;
|
|
3016
|
+
return c[0] >= b[0] && c[0] <= b[2] && c[1] >= b[1] && c[1] <= b[3] && area(b) > area(inner.b);
|
|
3017
|
+
}
|
|
3018
|
+
var TargetNotFoundError = class extends OniroError {
|
|
3019
|
+
constructor(message, reason) {
|
|
3020
|
+
super(message);
|
|
3021
|
+
this.reason = reason;
|
|
3022
|
+
this.name = "TargetNotFoundError";
|
|
3023
|
+
}
|
|
3024
|
+
reason;
|
|
3025
|
+
};
|
|
3026
|
+
function pickTarget(tree, selector, index) {
|
|
3027
|
+
const matches = findLayoutNodes(tree, selector).filter((n) => n.c !== void 0);
|
|
3028
|
+
const what = describeSelector(selector);
|
|
3029
|
+
if (matches.length === 0) {
|
|
3030
|
+
throw new TargetNotFoundError(`No on-screen element matches ${what}. Run \`oniro-app dump layout --flat\` to see what is there.`, "none");
|
|
3031
|
+
}
|
|
3032
|
+
const visible = matches.filter((n) => !n.covered);
|
|
3033
|
+
if (visible.length === 0) {
|
|
3034
|
+
throw new TargetNotFoundError(
|
|
3035
|
+
`Found ${matches.length} element(s) matching ${what}, but each is covered by an overlay (dialog, keyboard, or another layer): ${matches.map(describeLayoutNode).join("; ")}.`,
|
|
3036
|
+
"covered"
|
|
3037
|
+
);
|
|
3038
|
+
}
|
|
3039
|
+
const clickable = visible.filter((n) => n.click);
|
|
3040
|
+
const preferred = clickable.length > 0 ? clickable : visible;
|
|
3041
|
+
const candidates = preferred.filter((n) => !preferred.some((o) => o !== n && o.click && enclosesTarget(o, n)));
|
|
3042
|
+
if (index !== void 0) {
|
|
3043
|
+
const picked = candidates[index];
|
|
3044
|
+
if (!picked) {
|
|
3045
|
+
throw new TargetNotFoundError(`Index ${index} is out of range: ${candidates.length} element(s) match ${what}.`, "ambiguous");
|
|
3046
|
+
}
|
|
3047
|
+
return picked;
|
|
3048
|
+
}
|
|
3049
|
+
if (candidates.length > 1) {
|
|
3050
|
+
throw new TargetNotFoundError(
|
|
3051
|
+
`${candidates.length} elements match ${what}; pass an index to choose: ` + candidates.map((n, i) => `[${i}] ${describeLayoutNode(n)}`).join("; "),
|
|
3052
|
+
"ambiguous"
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
return candidates[0];
|
|
3056
|
+
}
|
|
2814
3057
|
async function dumpLayout(opts) {
|
|
2815
3058
|
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
2816
3059
|
const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
|
|
@@ -2828,9 +3071,13 @@ ${dump.stdout}`);
|
|
|
2828
3071
|
if (opts.prune === false) {
|
|
2829
3072
|
return { display, tree: parsed };
|
|
2830
3073
|
}
|
|
2831
|
-
const prunedRaw = pruneLayout(parsed, display);
|
|
3074
|
+
const prunedRaw = pruneLayout(parsed, display, findCoveredNodes(parsed));
|
|
2832
3075
|
const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
|
|
2833
|
-
return { display, tree };
|
|
3076
|
+
if (!opts.detectKeyboard || opts.bundleName) return { display, tree };
|
|
3077
|
+
const imeBundles = await getImeBundles(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
3078
|
+
const kb = findKeyboardWindow(tree, imeBundles);
|
|
3079
|
+
const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
|
|
3080
|
+
return { display, tree, keyboard };
|
|
2834
3081
|
} finally {
|
|
2835
3082
|
fs20.promises.unlink(local).catch(() => {
|
|
2836
3083
|
});
|
|
@@ -2839,6 +3086,161 @@ ${dump.stdout}`);
|
|
|
2839
3086
|
}
|
|
2840
3087
|
}
|
|
2841
3088
|
|
|
3089
|
+
// src/hdc/uiActions.ts
|
|
3090
|
+
var FOCUS_SETTLE_MS = 400;
|
|
3091
|
+
var KEYBOARD_SETTLE_MS = 400;
|
|
3092
|
+
var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
3093
|
+
function targetTree(layout) {
|
|
3094
|
+
const kb = layout.keyboard;
|
|
3095
|
+
return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
|
|
3096
|
+
}
|
|
3097
|
+
function fractionToPixel(f, size) {
|
|
3098
|
+
return Math.min(size - 1, Math.round(f * size));
|
|
3099
|
+
}
|
|
3100
|
+
function pressBack(opts) {
|
|
3101
|
+
return sendInput({ config: opts.config, type: "keyEvent", key: "Back", deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
3102
|
+
}
|
|
3103
|
+
async function hideKeyboard(opts) {
|
|
3104
|
+
const { keyboard } = await dumpLayout({ ...opts, detectKeyboard: true });
|
|
3105
|
+
if (!keyboard?.shown) return false;
|
|
3106
|
+
await pressBack(opts);
|
|
3107
|
+
await sleep(KEYBOARD_SETTLE_MS);
|
|
3108
|
+
return true;
|
|
3109
|
+
}
|
|
3110
|
+
async function locateTarget(opts) {
|
|
3111
|
+
assertLayoutSelector(opts.selector);
|
|
3112
|
+
const dumpOpts = { config: opts.config, detectKeyboard: true, deviceSerial: opts.deviceSerial, logger: opts.logger };
|
|
3113
|
+
let layout = await dumpLayout(dumpOpts);
|
|
3114
|
+
if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
|
|
3115
|
+
let keyboardHidden = false;
|
|
3116
|
+
let node;
|
|
3117
|
+
let failure;
|
|
3118
|
+
try {
|
|
3119
|
+
node = pickTarget(targetTree(layout), opts.selector, opts.index);
|
|
3120
|
+
} catch (err) {
|
|
3121
|
+
if (!(err instanceof TargetNotFoundError) || err.reason === "ambiguous") throw err;
|
|
3122
|
+
failure = err;
|
|
3123
|
+
}
|
|
3124
|
+
if ((!node || !node.click) && layout.keyboard?.shown && opts.hideKeyboard !== false) {
|
|
3125
|
+
opts.logger?.debug("target not reachable while the keyboard is up; dismissing it and looking again");
|
|
3126
|
+
await pressBack(opts);
|
|
3127
|
+
await sleep(KEYBOARD_SETTLE_MS);
|
|
3128
|
+
keyboardHidden = true;
|
|
3129
|
+
layout = await dumpLayout(dumpOpts);
|
|
3130
|
+
if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
|
|
3131
|
+
node = pickTarget(targetTree(layout), opts.selector, opts.index);
|
|
3132
|
+
}
|
|
3133
|
+
if (!node) throw failure;
|
|
3134
|
+
if (!node.c) throw new OniroError("The element has no on-screen position.");
|
|
3135
|
+
const x2 = fractionToPixel(node.c[0], layout.display.width);
|
|
3136
|
+
const y = fractionToPixel(node.c[1], layout.display.height);
|
|
3137
|
+
return { node, x: x2, y, display: layout.display, keyboardHidden };
|
|
3138
|
+
}
|
|
3139
|
+
function fraction(value, name) {
|
|
3140
|
+
if (value === void 0) return void 0;
|
|
3141
|
+
if (!(value >= 0 && value <= 1)) throw new OniroError(`${name} must be a fraction between 0 and 1; got ${value}.`);
|
|
3142
|
+
return value;
|
|
3143
|
+
}
|
|
3144
|
+
function pair(a, b, names) {
|
|
3145
|
+
if (a === void 0 && b === void 0) return void 0;
|
|
3146
|
+
if (a === void 0 || b === void 0) throw new OniroError(`Give both ${names}.`);
|
|
3147
|
+
return [a, b];
|
|
3148
|
+
}
|
|
3149
|
+
async function performInput(opts) {
|
|
3150
|
+
const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
|
|
3151
|
+
const startFx = pair(fraction(opts.fx, "--fx"), fraction(opts.fy, "--fy"), "--fx and --fy");
|
|
3152
|
+
const startPx = pair(opts.pxX, opts.pxY, "--x and --y");
|
|
3153
|
+
const endFx = pair(fraction(opts.fx2, "--fx2"), fraction(opts.fy2, "--fy2"), "--fx2 and --fy2");
|
|
3154
|
+
const endPx = pair(opts.pxX2, opts.pxY2, "--x2 and --y2");
|
|
3155
|
+
if ([opts.target, startFx, startPx].filter((s) => s !== void 0).length > 1) {
|
|
3156
|
+
throw new OniroError("Give the start point one way: a target, fractions (--fx/--fy), or pixels (--x/--y).");
|
|
3157
|
+
}
|
|
3158
|
+
if (endFx && endPx) throw new OniroError("Give the end point one way: fractions (--fx2/--fy2) or pixels (--x2/--y2).");
|
|
3159
|
+
let display;
|
|
3160
|
+
const toPx = async ([fx, fy]) => {
|
|
3161
|
+
display ??= await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
3162
|
+
if (!display) throw new OniroError("Could not read the display size to convert fractions to pixels.");
|
|
3163
|
+
return [fractionToPixel(fx, display.width), fractionToPixel(fy, display.height)];
|
|
3164
|
+
};
|
|
3165
|
+
const result = { keyboardHidden: false };
|
|
3166
|
+
let start = startPx;
|
|
3167
|
+
if (opts.target) {
|
|
3168
|
+
const located = await locateTarget({ ...device, selector: opts.target, index: opts.targetIndex });
|
|
3169
|
+
display = located.display;
|
|
3170
|
+
start = [located.x, located.y];
|
|
3171
|
+
result.target = located.node;
|
|
3172
|
+
result.keyboardHidden = located.keyboardHidden;
|
|
3173
|
+
} else if (startFx) {
|
|
3174
|
+
start = await toPx(startFx);
|
|
3175
|
+
}
|
|
3176
|
+
const end = endFx ? await toPx(endFx) : endPx;
|
|
3177
|
+
if (start) [result.x, result.y] = start;
|
|
3178
|
+
const tapToFocus = async () => {
|
|
3179
|
+
if (!start) return;
|
|
3180
|
+
await sendInput({ ...device, type: "click", pxX: start[0], pxY: start[1] });
|
|
3181
|
+
await sleep(FOCUS_SETTLE_MS);
|
|
3182
|
+
};
|
|
3183
|
+
const clearFocused = async () => {
|
|
3184
|
+
await sendInput({ ...device, type: "keyEvent", key: "Ctrl+A" });
|
|
3185
|
+
await sendInput({ ...device, type: "keyEvent", key: "Delete" });
|
|
3186
|
+
};
|
|
3187
|
+
switch (opts.type) {
|
|
3188
|
+
case "hideKeyboard":
|
|
3189
|
+
result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
|
|
3190
|
+
return result;
|
|
3191
|
+
case "clearText":
|
|
3192
|
+
await tapToFocus();
|
|
3193
|
+
await clearFocused();
|
|
3194
|
+
break;
|
|
3195
|
+
case "inputText":
|
|
3196
|
+
if (opts.clear) {
|
|
3197
|
+
await tapToFocus();
|
|
3198
|
+
await clearFocused();
|
|
3199
|
+
await sendInput({ ...device, type: "inputText", text: opts.text });
|
|
3200
|
+
} else {
|
|
3201
|
+
await sendInput({ ...device, type: "inputText", pxX: start?.[0], pxY: start?.[1], text: opts.text });
|
|
3202
|
+
}
|
|
3203
|
+
break;
|
|
3204
|
+
default:
|
|
3205
|
+
await sendInput({
|
|
3206
|
+
...device,
|
|
3207
|
+
type: opts.type,
|
|
3208
|
+
pxX: start?.[0],
|
|
3209
|
+
pxY: start?.[1],
|
|
3210
|
+
pxX2: end?.[0],
|
|
3211
|
+
pxY2: end?.[1],
|
|
3212
|
+
speed: opts.speed,
|
|
3213
|
+
key: opts.key,
|
|
3214
|
+
text: opts.text
|
|
3215
|
+
});
|
|
3216
|
+
}
|
|
3217
|
+
if (opts.hideKeyboard) result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
|
|
3218
|
+
return result;
|
|
3219
|
+
}
|
|
3220
|
+
async function waitForTarget(opts) {
|
|
3221
|
+
const { selector } = opts;
|
|
3222
|
+
assertLayoutSelector(selector);
|
|
3223
|
+
let found = null;
|
|
3224
|
+
await waitForCondition({
|
|
3225
|
+
probe: async () => {
|
|
3226
|
+
const layout = await dumpLayout({
|
|
3227
|
+
config: opts.config,
|
|
3228
|
+
detectKeyboard: true,
|
|
3229
|
+
deviceSerial: opts.deviceSerial,
|
|
3230
|
+
logger: opts.logger
|
|
3231
|
+
});
|
|
3232
|
+
found = findLayoutNodes(targetTree(layout), selector).find((n) => !n.covered) ?? null;
|
|
3233
|
+
return opts.gone ? found === null : found !== null;
|
|
3234
|
+
},
|
|
3235
|
+
timeoutMs: opts.timeoutMs,
|
|
3236
|
+
pollMs: opts.pollMs ?? 500,
|
|
3237
|
+
abortSignal: opts.abortSignal,
|
|
3238
|
+
logger: opts.logger,
|
|
3239
|
+
timeoutMessage: opts.gone ? `An element matching the selector was still on screen after ${opts.timeoutMs}ms.` : `No element matching the selector appeared within ${opts.timeoutMs}ms.`
|
|
3240
|
+
});
|
|
3241
|
+
return opts.gone ? null : found;
|
|
3242
|
+
}
|
|
3243
|
+
|
|
2842
3244
|
// src/hdc/hilog.ts
|
|
2843
3245
|
function setHilogLevel(opts) {
|
|
2844
3246
|
const logger = opts.logger ?? noopLogger;
|
|
@@ -4971,7 +5373,7 @@ async function harmonyOsAutoSign(opts) {
|
|
|
4971
5373
|
}
|
|
4972
5374
|
|
|
4973
5375
|
// src/index.ts
|
|
4974
|
-
var VERSION = "0.
|
|
5376
|
+
var VERSION = "0.13.0";
|
|
4975
5377
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4976
5378
|
0 && (module.exports = {
|
|
4977
5379
|
ALL_SDKS,
|
|
@@ -4991,9 +5393,11 @@ var VERSION = "0.12.0";
|
|
|
4991
5393
|
SIGNING_ERRORS,
|
|
4992
5394
|
SdkNotInstalledError,
|
|
4993
5395
|
TMP_DIR_HINT,
|
|
5396
|
+
TargetNotFoundError,
|
|
4994
5397
|
UnsupportedPlatformError,
|
|
4995
5398
|
VERSION,
|
|
4996
5399
|
applyChanges,
|
|
5400
|
+
assertLayoutSelector,
|
|
4997
5401
|
attemptHdcConnection,
|
|
4998
5402
|
buildCmdWrapper,
|
|
4999
5403
|
buildGestureCommands,
|
|
@@ -5016,6 +5420,7 @@ var VERSION = "0.12.0";
|
|
|
5016
5420
|
dedupEntries,
|
|
5017
5421
|
defaultContext,
|
|
5018
5422
|
defaultPaths,
|
|
5423
|
+
describeLayoutNode,
|
|
5019
5424
|
detectAaStartFailure,
|
|
5020
5425
|
detectProjectSdkVersion,
|
|
5021
5426
|
detectRuntimeOs,
|
|
@@ -5041,8 +5446,12 @@ var VERSION = "0.12.0";
|
|
|
5041
5446
|
extractZipWithProgress,
|
|
5042
5447
|
findAppProcessId,
|
|
5043
5448
|
findCmdToolsSourceDir,
|
|
5449
|
+
findCoveredNodes,
|
|
5044
5450
|
findHarmonyOsSdk,
|
|
5451
|
+
findKeyboardWindow,
|
|
5452
|
+
findLayoutNodes,
|
|
5045
5453
|
findRunningProcess,
|
|
5454
|
+
flattenLayout,
|
|
5046
5455
|
forceStop,
|
|
5047
5456
|
formatBytes,
|
|
5048
5457
|
generateSigningConfigs,
|
|
@@ -5057,6 +5466,7 @@ var VERSION = "0.12.0";
|
|
|
5057
5466
|
getFreeSpaceBytes,
|
|
5058
5467
|
getHdcPath,
|
|
5059
5468
|
getHvigorwPath,
|
|
5469
|
+
getImeBundles,
|
|
5060
5470
|
getInstalledSdks,
|
|
5061
5471
|
getKey,
|
|
5062
5472
|
getMainAbility,
|
|
@@ -5069,6 +5479,7 @@ var VERSION = "0.12.0";
|
|
|
5069
5479
|
harmonyOsAutoSign,
|
|
5070
5480
|
harmonyOsSdkLabelForApi,
|
|
5071
5481
|
hdcExec,
|
|
5482
|
+
hideKeyboard,
|
|
5072
5483
|
installApp,
|
|
5073
5484
|
installCmdTools,
|
|
5074
5485
|
installEmulator,
|
|
@@ -5085,6 +5496,7 @@ var VERSION = "0.12.0";
|
|
|
5085
5496
|
listModules,
|
|
5086
5497
|
listRunningProcesses,
|
|
5087
5498
|
listTemplates,
|
|
5499
|
+
locateTarget,
|
|
5088
5500
|
needsCmdWrapper,
|
|
5089
5501
|
nonInteractivePrompter,
|
|
5090
5502
|
noopLogger,
|
|
@@ -5095,9 +5507,12 @@ var VERSION = "0.12.0";
|
|
|
5095
5507
|
parseCodelinterFindings,
|
|
5096
5508
|
parseDeviceList,
|
|
5097
5509
|
parseHilogLine,
|
|
5510
|
+
parseImeBundles,
|
|
5098
5511
|
parseMainAbilityFromBmDump,
|
|
5099
5512
|
parseRemoteExit,
|
|
5513
|
+
performInput,
|
|
5100
5514
|
pickSigningKind,
|
|
5515
|
+
pickTarget,
|
|
5101
5516
|
prepareSigning,
|
|
5102
5517
|
pruneLayout,
|
|
5103
5518
|
readJson5File,
|
|
@@ -5110,6 +5525,7 @@ var VERSION = "0.12.0";
|
|
|
5110
5525
|
removeSdk,
|
|
5111
5526
|
removeTempWorkDir,
|
|
5112
5527
|
requireHarmonyOsSdk,
|
|
5528
|
+
resolveKeyEvent,
|
|
5113
5529
|
resolveTmpRoot,
|
|
5114
5530
|
runCodelinter,
|
|
5115
5531
|
runHvigorw,
|
|
@@ -5139,8 +5555,10 @@ var VERSION = "0.12.0";
|
|
|
5139
5555
|
waitForBundle,
|
|
5140
5556
|
waitForCondition,
|
|
5141
5557
|
waitForLog,
|
|
5558
|
+
waitForTarget,
|
|
5142
5559
|
watchLog,
|
|
5143
5560
|
waypointsToRawEvents,
|
|
5561
|
+
withoutWindow,
|
|
5144
5562
|
writeJson5File,
|
|
5145
5563
|
writeJsonFile
|
|
5146
5564
|
});
|